├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── deployment.py ├── plasma ├── __init__.py ├── child_chain │ ├── __init__.py │ ├── child_chain.py │ ├── root_event_listener.py │ └── server.py ├── cli │ ├── __init__.py │ └── cli.py ├── client │ ├── __init__.py │ ├── child_chain_service.py │ ├── client.py │ └── exceptions.py └── root_chain │ ├── __init__.py │ ├── contracts │ ├── ByteUtils.sol │ ├── ECRecovery.sol │ ├── Math.sol │ ├── Merkle.sol │ ├── PlasmaRLP.sol │ ├── PriorityQueue.sol │ ├── RLPDecode.sol │ ├── RLPEncode.sol │ ├── RootChain.sol │ ├── SafeMath.sol │ └── Validate.sol │ └── deployer.py ├── plasma_core ├── __init__.py ├── block.py ├── chain.py ├── constants.py ├── exceptions.py ├── transaction.py └── utils │ ├── __init__.py │ ├── address.py │ ├── merkle │ ├── __init__.py │ ├── exceptions.py │ ├── fixed_merkle.py │ └── node.py │ ├── signatures.py │ ├── transactions.py │ └── utils.py ├── setup.cfg ├── setup.py ├── testlang ├── __init__.py └── testing_language.py └── tests ├── child_chain ├── test_block.py ├── test_child_chain.py ├── test_child_chain_integration.py └── test_transaction.py ├── conftest.py ├── root_chain └── contracts │ ├── data_structures │ └── test_priority_queue.py │ └── root_chain │ └── test_root_chain.py └── utils └── test_fixed_merkle.py /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure that tests pass and code is lint free: `make test && make lint` 11 | 2. Update the README.md if any changes invalidate its current content. 12 | 3. Include any tests for new functionality. 13 | 4. Reference any revelant issues in your PR comment. 14 | 15 | ## Code of Conduct 16 | 17 | ### Our Pledge 18 | 19 | In the interest of fostering an open and welcoming environment, we as 20 | contributors and maintainers pledge to making participation in our project and 21 | our community a harassment-free experience for everyone, regardless of age, body 22 | size, disability, ethnicity, gender identity and expression, level of experience, 23 | nationality, personal appearance, race, religion, or sexual identity and 24 | orientation. 25 | 26 | ### Our Standards 27 | 28 | Examples of behavior that contributes to creating a positive environment 29 | include: 30 | 31 | * Using welcoming and inclusive language 32 | * Being respectful of differing viewpoints and experiences 33 | * Gracefully accepting constructive criticism 34 | * Focusing on what is best for the community 35 | * Showing empathy towards other community members 36 | 37 | Examples of unacceptable behavior by participants include: 38 | 39 | * The use of sexualized language or imagery and unwelcome sexual attention or 40 | advances 41 | * Trolling, insulting/derogatory comments, and personal or political attacks 42 | * Public or private harassment 43 | * Publishing others' private information, such as a physical or electronic 44 | address, without explicit permission 45 | * Other conduct which could reasonably be considered inappropriate in a 46 | professional setting 47 | 48 | ### Our Responsibilities 49 | 50 | Project maintainers are responsible for clarifying the standards of acceptable 51 | behavior and are expected to take appropriate and fair corrective action in 52 | response to any instances of unacceptable behavior. 53 | 54 | Project maintainers have the right and responsibility to remove, edit, or 55 | reject comments, commits, code, wiki edits, issues, and other contributions 56 | that are not aligned to this Code of Conduct, or to ban temporarily or 57 | permanently any contributor for other behaviors that they deem inappropriate, 58 | threatening, offensive, or harmful. 59 | 60 | ### Scope 61 | 62 | This Code of Conduct applies both within project spaces and in public spaces 63 | when an individual is representing the project or its community. Examples of 64 | representing a project or community include using an official project e-mail 65 | address, posting via an official social media account, or acting as an appointed 66 | representative at an online or offline event. Representation of a project may be 67 | further defined and clarified by project maintainers. 68 | 69 | ### Enforcement 70 | 71 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 72 | reported by contacting the project team at plasma.team@omise.co. All 73 | complaints will be reviewed and investigated and will result in a response that 74 | is deemed necessary and appropriate to the circumstances. The project team is 75 | obligated to maintain confidentiality with regard to the reporter of an incident. 76 | Further details of specific enforcement policies may be posted separately. 77 | 78 | Project maintainers who do not follow or enforce the Code of Conduct in good 79 | faith may face temporary or permanent repercussions as determined by other 80 | members of the project's leadership. 81 | 82 | ### Attribution 83 | 84 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 85 | available at [http://contributor-covenant.org/version/1/4][version] and from the [Angular Seed Contributing Guide][angular-contrib]. 86 | 87 | [homepage]: http://contributor-covenant.org 88 | [version]: http://contributor-covenant.org/version/1/4/ 89 | [angular-contrib]: https://github.com/mgechev/angular-seed/blob/master/.github/CONTRIBUTING.md 90 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## Issue Type 6 | 7 | 8 | ``` 9 | [ ] bug report 10 | [ ] feature request 11 | ``` 12 | 13 | ## Current Behavior 14 | 15 | 16 | 17 | ## Expected Behavior 18 | 19 | 20 | 21 | ## Steps to Reproduce 22 | 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 28 | - Full output of error: 29 | - Command that caused error: 30 | - Code that caused error: 31 | 32 | 33 | ## Suggested Fix 34 | 35 | 36 | 37 | ## Motivation for Change 38 | 39 | 40 | 41 | ## System Specs 42 | 43 | 44 | - python version: 45 | - pyetherem version: 46 | - environment (output of running `pip freeze`): 47 | - operating system: 48 | 49 | 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Bytecode 2 | **/__pycache__/ 3 | 4 | # Development 5 | env*/ 6 | .vscode/ 7 | .DS_Store 8 | 9 | # Distibution 10 | dist/ 11 | *.egg-info/ 12 | 13 | # Testing 14 | .cache/ 15 | .pytest_cache/ 16 | contract_data/ 17 | 18 | *.pyc 19 | build/ 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.6" 5 | 6 | cache: pip 7 | 8 | env: 9 | NODE_VERSION="8" 10 | 11 | install: 12 | - nvm install $NODE_VERSION 13 | - npm install --global ganache-cli 14 | - wget https://github.com/ethereum/solidity/releases/download/v0.5.4/solc-static-linux && chmod +x ./solc-static-linux && sudo mv solc-static-linux /usr/bin/solc 15 | - pip install --upgrade pip setuptools flake8 16 | - make 17 | 18 | script: 19 | - (ganache-cli -m="plasma_mvp" &) && while ! curl -s localhost:8545 >/dev/null; do sleep 1; done 20 | - make test 21 | - make lint 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 OmiseGO Pte. Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | python setup.py install 3 | 4 | .PHONY: help 5 | help: 6 | @echo "root-chain - deploys the root chain contract" 7 | @echo "child-chain - starts the child chain" 8 | @echo "clean - remove build artifacts" 9 | @echo "lint - check style with flake8" 10 | @echo "test - run tests with pytest" 11 | 12 | .PHONY: root-chain 13 | root-chain: 14 | python deployment.py 15 | 16 | .PHONY: child-chain 17 | child-chain: 18 | PYTHONPATH=. python plasma/child_chain/server.py 19 | 20 | .PHONY: clean 21 | clean: clean-build clean-pyc 22 | 23 | .PHONY: clean-build 24 | clean-build: 25 | rm -fr build/ 26 | rm -fr dist/ 27 | rm -fr *.egg-info 28 | 29 | .PHONY: clean-pyc 30 | clean-pyc: 31 | find . -name '*.pyc' -exec rm -f {} + 32 | find . -name '*.pyo' -exec rm -f {} + 33 | find . -name '*~' -exec rm -f {} + 34 | find . -name '*pycache__' -exec rm -rf {} + 35 | find . -name '.pytest_cache' -exec rm -rf {} + 36 | 37 | .PHONY: lint 38 | lint: 39 | flake8 plasma plasma_core testlang tests 40 | 41 | .PHONY: test 42 | test: 43 | python -m pytest 44 | find . -name '.pytest_cache' -exec rm -rf {} + 45 | 46 | .PHONY: dev 47 | dev: 48 | pip install pytest pylint flake8 49 | 50 | .PHONY: ganache 51 | ganache: 52 | ganache-cli -m="plasma_mvp" 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notice! 2 | This is an old research repo. No active work is being done here. Efforts in the direction of production-ready MVP plasma chain (MoreVP, ERC20, audits) are in https://github.com/omisego/plasma-contracts. 3 | 4 | # Plasma MVP 5 | 6 | We're implementing [Minimum Viable Plasma](https://ethresear.ch/t/minimal-viable-plasma/426). This repository represents a work in progress and will undergo large-scale modifications as requirements change. 7 | 8 | ## Overview 9 | 10 | Plasma MVP is split into four main parts: `root_chain`, `child_chain`, `client`, and `cli`. Below is an overview of each sub-project. 11 | 12 | ### root_chain 13 | 14 | `root_chain` represents the Plasma contract to be deployed to the root blockchain. In our case, this contract is written in Solidity and is designed to be deployed to Ethereum. `root_chain` also includes a compilation/deployment script. 15 | 16 | `RootChain.sol` is based off of the Plasma design specified in [Minimum Viable Plasma](https://ethresear.ch/t/minimal-viable-plasma/426). Currently, this contract allows a single authority to publish child chain blocks to the root chain. This is *not* a permanent design and is intended to simplify development of more critical components in the short term. 17 | 18 | ### child_chain 19 | 20 | `child_chain` is a Python implementation of a Plasma MVP child chain client. It's useful to think of `child_chain` as analogous to [Parity](https://www.parity.io) or [Geth](https://geth.ethereum.org). This component manages a store of `Blocks` and `Transactions` that are updated when events are fired in the root contract. 21 | 22 | `child_chain` also contains an RPC server that enables client interactions. By default, this server runs on port `8546`. 23 | 24 | ### client 25 | 26 | `client` is a simple Python wrapper of the RPC API exposed by `child_chain`, similar to `Web3.py` for Ethereum. You can use this client to write Python applications that interact with this Plasma chain. 27 | 28 | ### cli 29 | 30 | `cli` is a simple Python application that uses `client` to interact with `child_chain`, via the command line. A detailed documentation of `cli` is available [here](#cli-documentation). 31 | 32 | ## Getting Started 33 | 34 | ### Dependencies 35 | 36 | This project has a few pre-installation dependencies. 37 | 38 | #### [Solidity ^0.5.0](https://solidity.readthedocs.io/en/latest/installing-solidity.html) 39 | 40 | Mac: 41 | ```sh 42 | brew update 43 | brew upgrade 44 | brew tap ethereum/ethereum 45 | brew install solidity 46 | ``` 47 | 48 | Linux: 49 | ```sh 50 | sudo add-apt-repository ppa:ethereum/ethereum 51 | sudo apt-get update 52 | sudo apt-get install solc 53 | ``` 54 | 55 | Windows: 56 | 57 | Follow [this guide](https://solidity.readthedocs.io/en/latest/installing-solidity.html#prerequisites-windows) 58 | 59 | 60 | #### [Python 3.2+](https://www.python.org/downloads/) 61 | 62 | Mac: 63 | ```sh 64 | brew install python 65 | ``` 66 | 67 | Linux: 68 | ```sh 69 | sudo apt-get install software-properties-common 70 | sudo add-apt-repository ppa:deadsnakes/ppa 71 | sudo apt-get update 72 | sudo apt-get install python3 73 | ``` 74 | 75 | Windows: 76 | ```sh 77 | choco install python 78 | ``` 79 | 80 | #### [Ganache CLI 6.1.8+](https://github.com/trufflesuite/ganache-cli) 81 | 82 | ### Installation 83 | 84 | Note: we optionally recommend using something like [`virtualenv`](https://pypi.python.org/pypi/virtualenv) in order to create an isolated Python environment: 85 | 86 | ``` 87 | $ virtualenv env -p python3 88 | ``` 89 | 90 | Fetch and install the project's dependencies with: 91 | 92 | ``` 93 | $ make 94 | ``` 95 | 96 | ### Testing 97 | 98 | Before you run tests, make sure you have an Ethereum client running and an JSON RPC API exposed on port `8545`. We recommend using `ganache-cli` to accomplish this when running tests. Start it with the command-line argument `-m="plasma_mvp"`. 99 | 100 | Project tests can be found in the `tests/` folder. Run tests with: 101 | 102 | ``` 103 | $ make test 104 | ``` 105 | 106 | If you're contributing to this project, make sure you also install [`flake8`](https://pypi.org/project/flake8/) and lint your work: 107 | 108 | ``` 109 | $ make lint 110 | ``` 111 | 112 | ### Starting Plasma 113 | 114 | The fastest way to start playing with our Plasma MVP is by starting up `ganache-cli`, deploying everything locally, and running our CLI. Full documentation for the CLI is available [here](#cli-documentation). 115 | 116 | ```bash 117 | $ ganache-cli -m=plasma_mvp # Start ganache-cli 118 | $ make root-chain # Deploy the root chain contract 119 | $ make child-chain # Run our child chain and server 120 | ``` 121 | 122 | ## CLI Documentation 123 | 124 | `omg` is a simple Plasma CLI that enables interactions with the child chain. Full documentation is provided below. 125 | 126 | ### `help` 127 | 128 | #### Description 129 | 130 | Shows a list of available commands. 131 | 132 | #### Usage 133 | 134 | ``` 135 | --help 136 | ``` 137 | 138 | ### `deposit` 139 | 140 | #### Description 141 | 142 | Creates a deposit transaction and submits it to the child chain. 143 | 144 | #### Usage 145 | 146 | ``` 147 | deposit
148 | ``` 149 | 150 | #### Example 151 | 152 | ``` 153 | deposit 100 0xfd02EcEE62797e75D86BCff1642EB0844afB28c7 154 | ``` 155 | 156 | ### `sendtx` 157 | 158 | #### Description 159 | 160 | Creates a transaction and submits it to the child chain. 161 | 162 | #### Usage 163 | 164 | ``` 165 | sendtx [] 166 | ``` 167 | 168 | #### Example 169 | 170 | ``` 171 | sendtx 1 0 0 0 0 0 0x0 0xfd02EcEE62797e75D86BCff1642EB0844afB28c7 50 0x4B3eC6c9dC67079E82152d6D55d8dd96a8e6AA26 45 3bb369fecdc16b93b99514d8ed9c2e87c5824cf4a6a98d2e8e91b7dd0c063304 172 | ``` 173 | 174 | ### `submitblock` 175 | 176 | #### Description 177 | 178 | Signs and submits the current block to the root contract. 179 | 180 | #### Usage 181 | 182 | ``` 183 | submitblock 184 | ``` 185 | 186 | #### Example 187 | 188 | ``` 189 | submitblock 3bb369fecdc16b93b99514d8ed9c2e87c5824cf4a6a98d2e8e91b7dd0c063304 190 | ``` 191 | 192 | ### `withdraw` 193 | 194 | #### Description 195 | 196 | Creates an exit transaction for the given UTXO. 197 | 198 | #### Usage 199 | 200 | ``` 201 | withdraw [] 202 | ``` 203 | 204 | #### Example 205 | 206 | ``` 207 | withdraw 1000 0 0 3bb369fecdc16b93b99514d8ed9c2e87c5824cf4a6a98d2e8e91b7dd0c063304 208 | ``` 209 | 210 | ### `withdrawdeposit` 211 | 212 | #### Description 213 | 214 | Withdraws from a deposit. 215 | 216 | #### Usage 217 | 218 | ``` 219 | withdrawdeposit 220 | ``` 221 | 222 | #### Example 223 | 224 | ``` 225 | withdrawdeposit 0xfd02EcEE62797e75D86BCff1642EB0844afB28c7 1 100 226 | ``` 227 | 228 | 229 | ## CLI Example 230 | 231 | Let's play around a bit: 232 | 233 | 1. Deploy the root chain contract and start the child chain as per [Starting Plasma](#starting-plasma). 234 | 235 | 2. Start by depositing: 236 | ``` 237 | omg deposit 100 0xfd02EcEE62797e75D86BCff1642EB0844afB28c7 238 | ``` 239 | 240 | 3. Send a transaction: 241 | ``` 242 | omg sendtx 1 0 0 0 0 0 0x0 0xfd02EcEE62797e75D86BCff1642EB0844afB28c7 50 0x4B3eC6c9dC67079E82152d6D55d8dd96a8e6AA26 45 3bb369fecdc16b93b99514d8ed9c2e87c5824cf4a6a98d2e8e91b7dd0c063304 243 | ``` 244 | 245 | 4. Submit the block: 246 | ``` 247 | omg submitblock 3bb369fecdc16b93b99514d8ed9c2e87c5824cf4a6a98d2e8e91b7dd0c063304 248 | ``` 249 | 250 | 5. Withdraw the original deposit (this is a double spend!): 251 | 252 | ``` 253 | omg withdrawdeposit 0xfd02EcEE62797e75D86BCff1642EB0844afB28c7 1 100 254 | ``` 255 | 256 | Note: The functionality to challenge double spends from the cli is still being worked on. 257 | -------------------------------------------------------------------------------- /deployment.py: -------------------------------------------------------------------------------- 1 | from plasma.root_chain.deployer import Deployer 2 | 3 | deployer = Deployer() 4 | deployer.compile_all() 5 | deployer.deploy_contract("RootChain") 6 | -------------------------------------------------------------------------------- /plasma/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma/__init__.py -------------------------------------------------------------------------------- /plasma/child_chain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma/child_chain/__init__.py -------------------------------------------------------------------------------- /plasma/child_chain/child_chain.py: -------------------------------------------------------------------------------- 1 | from plasma_core.block import Block 2 | from plasma_core.chain import Chain 3 | from plasma_core.utils.transactions import get_deposit_tx, encode_utxo_id 4 | from .root_event_listener import RootEventListener 5 | 6 | 7 | class ChildChain(object): 8 | 9 | def __init__(self, operator, root_chain): 10 | self.operator = operator 11 | self.root_chain = root_chain 12 | self.chain = Chain(self.operator) 13 | self.current_block = Block(number=self.chain.next_child_block) 14 | 15 | # Listen for events 16 | self.event_listener = RootEventListener(root_chain, confirmations=0) 17 | self.event_listener.on('Deposit', self.apply_deposit) 18 | self.event_listener.on('ExitStarted', self.apply_exit) 19 | 20 | def apply_exit(self, event): 21 | event_args = event['args'] 22 | utxo_id = event_args['utxoPos'] 23 | self.chain.mark_utxo_spent(utxo_id) 24 | 25 | def apply_deposit(self, event): 26 | event_args = event['args'] 27 | owner = event_args['depositor'] 28 | amount = event_args['amount'] 29 | blknum = event_args['depositBlock'] 30 | 31 | deposit_tx = get_deposit_tx(owner, amount) 32 | deposit_block = Block([deposit_tx], number=blknum) 33 | self.chain.add_block(deposit_block) 34 | 35 | def apply_transaction(self, tx): 36 | self.chain.validate_transaction(tx, self.current_block.spent_utxos) 37 | self.current_block.add_transaction(tx) 38 | return encode_utxo_id(self.current_block.number, len(self.current_block.transaction_set) - 1, 0) 39 | 40 | def submit_block(self, block): 41 | self.chain.add_block(block) 42 | self.root_chain.transact({ 43 | 'from': self.operator 44 | }).submitBlock(block.merkle.root) 45 | self.current_block = Block(number=self.chain.next_child_block) 46 | 47 | def get_transaction(self, tx_id): 48 | return self.chain.get_transaction(tx_id) 49 | 50 | def get_block(self, blknum): 51 | return self.chain.get_block(blknum) 52 | 53 | def get_current_block(self): 54 | return self.current_block 55 | -------------------------------------------------------------------------------- /plasma/child_chain/root_event_listener.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | import json 4 | import threading 5 | from web3 import Web3, HTTPProvider 6 | from hashlib import sha256 7 | from hexbytes import HexBytes 8 | from web3.utils.datastructures import AttributeDict 9 | 10 | 11 | class RootEventListener(object): 12 | """Listens to events on the root chain. 13 | 14 | We abstract the logic for listening to events because 15 | we only want events to be acted upon once they're considered 16 | finalized. Events are known to be missed accidentally sometimes, 17 | so we try to make event listening more robust. 18 | 19 | Args: 20 | root_chain (ConciseContract): A Web3 ConciseContract representing the root chain. 21 | w3 (Web3): A Web3 object. 22 | finality (int): Number of blocks before events should be considered final. 23 | """ 24 | 25 | def __init__(self, root_chain, w3=Web3(HTTPProvider('http://localhost:8545')), confirmations=6): 26 | self.root_chain = root_chain 27 | self.w3 = w3 28 | self.confirmations = confirmations 29 | 30 | self.seen_events = {} 31 | self.active_events = {} 32 | self.subscribers = {} 33 | 34 | self.__listen_for_event('Deposit') 35 | self.__listen_for_event('ExitStarted') 36 | 37 | def on(self, event_name, event_handler): 38 | """Registers an event handler to an event by name. 39 | 40 | Event handlers are passed the Web3 Event dict. 41 | 42 | Args: 43 | event_name (str): Name of the event to listen to. 44 | event_handler (function): A function to call when the event is caught. 45 | """ 46 | 47 | self.subscribers[event_name].append(event_handler) 48 | 49 | def __listen_for_event(self, event_name): 50 | """Registers an event as being watched for and starts a filter loop. 51 | 52 | Args: 53 | event_name (str): Name of the event to watch for. 54 | """ 55 | 56 | self.subscribers[event_name] = [] 57 | self.active_events[event_name] = True 58 | threading.Thread(target=self.filter_loop, args=(event_name,)).start() 59 | 60 | def stop_listening_for_event(self, event_name): 61 | """Stops watching for a certain event. 62 | 63 | Args: 64 | event_name (str): Name of event to deregister. 65 | """ 66 | 67 | del self.active_events[event_name] 68 | 69 | def stop_all(self): 70 | """Stops watching for all events 71 | """ 72 | 73 | for event in list(self.active_events): 74 | self.stop_listening_for_event(event) 75 | 76 | def filter_loop(self, event_name): 77 | """Starts a filter loop to broadcast events. 78 | 79 | Note that we only watch for events that occur between 80 | `confirmations` and `confirmations * 2`. This is important because 81 | we never want a client to act on an event that isn't 82 | finalized. We might catch the same event twice, so we hash 83 | each event and make sure we haven't seen that event yet before 84 | broadcasting 85 | 86 | Args: 87 | event_name (str): Name of event to watch. 88 | """ 89 | 90 | while event_name in self.active_events: 91 | current_block = self.w3.eth.getBlock('latest') 92 | 93 | event_filter = self.root_chain.eventFilter(event_name, { 94 | 'fromBlock': current_block['number'] - (self.confirmations * 2 + 1), 95 | 'toBlock': current_block['number'] + 1 - self.confirmations 96 | }) 97 | 98 | for event in event_filter.get_all_entries(): 99 | event_hash = self.__hash_event(event) 100 | if event_hash not in self.seen_events: 101 | self.seen_events[event_hash] = True 102 | self.broadcast_event(event_name, event) 103 | 104 | time.sleep(random.random()) 105 | 106 | def broadcast_event(self, event_name, event): 107 | """Broadcasts an event to all subscribers. 108 | 109 | Args: 110 | event_name (str): Name of event to broadcast. 111 | event (dict): Event data to broadcast. 112 | """ 113 | 114 | for subscriber in self.subscribers[event_name]: 115 | subscriber(event) 116 | 117 | def __hash_event(self, event): 118 | """Returns the sha256 hash of an event dict. 119 | 120 | Args: 121 | event (dict): Event dict to hash. 122 | 123 | Returns: 124 | str: Hexadecimal hash string. 125 | """ 126 | 127 | # HACK: Be able to JSON serialize the AttributeDict/HexBytes objects https://github.com/ethereum/web3.py/issues/782 128 | 129 | class CustomJsonEncoder(json.JSONEncoder): 130 | def default(self, obj): # pylint: disable=E0202 131 | if isinstance(obj, AttributeDict): 132 | return obj.__dict__ 133 | if isinstance(obj, HexBytes): 134 | return obj.hex() 135 | return super().default(obj) 136 | 137 | stringified_event = json.dumps(dict(event), sort_keys=True, cls=CustomJsonEncoder) 138 | return sha256(stringified_event.encode()).hexdigest() 139 | -------------------------------------------------------------------------------- /plasma/child_chain/server.py: -------------------------------------------------------------------------------- 1 | import rlp 2 | from werkzeug.wrappers import Request, Response 3 | from werkzeug.serving import run_simple 4 | from jsonrpc import JSONRPCResponseManager, dispatcher 5 | from ethereum import utils 6 | from plasma.child_chain.child_chain import ChildChain 7 | from plasma.root_chain.deployer import Deployer 8 | from plasma_core.constants import CONTRACT_ADDRESS, AUTHORITY 9 | from plasma_core.block import Block 10 | from plasma_core.transaction import Transaction 11 | from plasma_core.utils.transactions import encode_utxo_id 12 | 13 | root_chain = Deployer().get_contract_at_address("RootChain", CONTRACT_ADDRESS, concise=False) 14 | child_chain = ChildChain(AUTHORITY['address'], root_chain) 15 | 16 | 17 | @Request.application 18 | def application(request): 19 | # Dispatcher is dictionary {: callable} 20 | dispatcher["submit_block"] = lambda block: child_chain.submit_block(rlp.decode(utils.decode_hex(block), Block)) 21 | dispatcher["apply_transaction"] = lambda transaction: child_chain.apply_transaction(rlp.decode(utils.decode_hex(transaction), Transaction)) 22 | dispatcher["get_transaction"] = lambda blknum, txindex: rlp.encode(child_chain.get_transaction(encode_utxo_id(blknum, txindex, 0)), Transaction).hex() 23 | dispatcher["get_current_block"] = lambda: rlp.encode(child_chain.get_current_block(), Block).hex() 24 | dispatcher["get_current_block_num"] = lambda: child_chain.get_current_block_num() 25 | dispatcher["get_block"] = lambda blknum: rlp.encode(child_chain.get_block(blknum), Block).hex() 26 | response = JSONRPCResponseManager.handle( 27 | request.data, dispatcher) 28 | return Response(response.json, mimetype='application/json') 29 | 30 | 31 | if __name__ == '__main__': 32 | run_simple('localhost', 8546, application) 33 | -------------------------------------------------------------------------------- /plasma/cli/__init__.py: -------------------------------------------------------------------------------- 1 | from .cli import cli # Noqa F401 2 | -------------------------------------------------------------------------------- /plasma/cli/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | from ethereum import utils 3 | from plasma_core.constants import NULL_ADDRESS 4 | from plasma_core.transaction import Transaction 5 | from plasma_core.utils.utils import confirm_tx 6 | from plasma_core.utils.transactions import encode_utxo_id 7 | from plasma.client.client import Client 8 | from plasma.client.exceptions import ChildChainServiceError 9 | 10 | 11 | CONTEXT_SETTINGS = dict( 12 | help_option_names=['-h', '--help'] 13 | ) 14 | 15 | 16 | @click.group(context_settings=CONTEXT_SETTINGS) 17 | @click.pass_context 18 | def cli(ctx): 19 | ctx.obj = Client() 20 | 21 | 22 | def client_call(fn, argz=(), successmessage=""): 23 | try: 24 | output = fn(*argz) 25 | if successmessage: 26 | print(successmessage) 27 | return output 28 | except ChildChainServiceError as err: 29 | print("Error:", err) 30 | print("additional details can be found from the child chain's server output") 31 | 32 | 33 | @cli.command() 34 | @click.argument('amount', required=True, type=int) 35 | @click.argument('address', required=True) 36 | @click.pass_obj 37 | def deposit(client, amount, address): 38 | client.deposit(amount, address) 39 | print("Deposited {0} to {1}".format(amount, address)) 40 | 41 | 42 | @cli.command() 43 | @click.argument('blknum1', type=int) 44 | @click.argument('txindex1', type=int) 45 | @click.argument('oindex1', type=int) 46 | @click.argument('blknum2', type=int) 47 | @click.argument('txindex2', type=int) 48 | @click.argument('oindex2', type=int) 49 | @click.argument('cur12', default="0x0") 50 | @click.argument('newowner1') 51 | @click.argument('amount1', type=int) 52 | @click.argument('newowner2') 53 | @click.argument('amount2', type=int) 54 | @click.argument('key1') 55 | @click.argument('key2', required=False) 56 | @click.pass_obj 57 | def sendtx(client, 58 | blknum1, txindex1, oindex1, 59 | blknum2, txindex2, oindex2, 60 | cur12, 61 | amount1, newowner1, 62 | amount2, newowner2, 63 | key1, key2): 64 | if cur12 == "0x0": 65 | cur12 = NULL_ADDRESS 66 | if newowner1 == "0x0": 67 | newowner1 = NULL_ADDRESS 68 | if newowner2 == "0x0": 69 | newowner2 = NULL_ADDRESS 70 | 71 | # Form a transaction 72 | tx = Transaction(blknum1, txindex1, oindex1, 73 | blknum2, txindex2, oindex2, 74 | utils.normalize_address(cur12), 75 | utils.normalize_address(newowner1), amount1, 76 | utils.normalize_address(newowner2), amount2) 77 | 78 | # Sign it 79 | if key1: 80 | tx.sign1(utils.normalize_key(key1)) 81 | if key2: 82 | tx.sign2(utils.normalize_key(key2)) 83 | 84 | client_call(client.apply_transaction, [tx], "Sent transaction") 85 | 86 | 87 | @cli.command() 88 | @click.argument('key', required=True) 89 | @click.pass_obj 90 | def submitblock(client, key): 91 | 92 | # Get the current block, already decoded by client 93 | block = client_call(client.get_current_block) 94 | 95 | # Sign the block 96 | block.make_mutable() 97 | normalized_key = utils.normalize_key(key) 98 | block.sign(normalized_key) 99 | 100 | client_call(client.submit_block, [block], "Submitted current block") 101 | 102 | 103 | @cli.command() 104 | @click.argument('blknum', required=True, type=int) 105 | @click.argument('txindex', required=True, type=int) 106 | @click.argument('oindex', required=True, type=int) 107 | @click.argument('key1') 108 | @click.argument('key2', required=False) 109 | @click.pass_obj 110 | def withdraw(client, 111 | blknum, txindex, oindex, 112 | key1, key2): 113 | 114 | # Get the transaction's block, already decoded by client 115 | block = client_call(client.get_block, [blknum]) 116 | 117 | # Create a Merkle proof 118 | tx = block.transaction_set[txindex] 119 | proof = block.merkle.create_membership_proof(tx.merkle_hash) 120 | 121 | # Create the confirmation signatures 122 | confirmSig1, confirmSig2 = b'', b'' 123 | if key1: 124 | confirmSig1 = confirm_tx(tx, block.merkle.root, utils.normalize_key(key1)) 125 | if key2: 126 | confirmSig2 = confirm_tx(tx, block.merkle.root, utils.normalize_key(key2)) 127 | sigs = tx.sig1 + tx.sig2 + confirmSig1 + confirmSig2 128 | 129 | client.withdraw(blknum, txindex, oindex, tx, proof, sigs) 130 | print("Submitted withdrawal") 131 | 132 | 133 | @cli.command() 134 | @click.argument('owner', required=True) 135 | @click.argument('blknum', required=True, type=int) 136 | @click.argument('amount', required=True, type=int) 137 | @click.pass_obj 138 | def withdrawdeposit(client, owner, blknum, amount): 139 | deposit_pos = encode_utxo_id(blknum, 0, 0) 140 | client.withdraw_deposit(owner, deposit_pos, amount) 141 | print("Submitted withdrawal") 142 | 143 | 144 | @cli.command() 145 | @click.argument('account', required=True) 146 | @click.pass_obj 147 | def finalize_exits(client, account): 148 | client.finalize_exits(account) 149 | print("Submitted finalizeExits") 150 | 151 | 152 | @cli.command() 153 | @click.argument('blknum', required=True, type=int) 154 | @click.argument('key', required=True) 155 | @click.pass_obj 156 | def confirm_sig(client, blknum, key): 157 | block = client_call(client.get_block, [blknum]) 158 | tx = client_call(client.get_transaction, [blknum, 0]) 159 | _key = utils.normalize_key(key) 160 | confirmSig = confirm_tx(tx, block.root, _key) 161 | print("confirm sig:", utils.encode_hex(confirmSig)) 162 | 163 | 164 | @cli.command() 165 | @click.argument('blknum', required=True, type=int) 166 | @click.argument('txindex', required=True, type=int) 167 | @click.argument('oindex', required=True, type=int) 168 | @click.argument('confirm_sig_hex', required=True) 169 | @click.argument('account', required=True) 170 | @click.pass_obj 171 | def challenge_exit(client, blknum, txindex, oindex, confirm_sig_hex, account): 172 | confirmSig = utils.decode_hex(confirm_sig_hex) 173 | client.challenge_exit(blknum, txindex, oindex, confirmSig, account) 174 | print("Submitted challenge exit") 175 | 176 | 177 | if __name__ == '__main__': 178 | cli() 179 | -------------------------------------------------------------------------------- /plasma/client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma/client/__init__.py -------------------------------------------------------------------------------- /plasma/client/child_chain_service.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import rlp 3 | from plasma.child_chain.child_chain import ChildChain 4 | from plasma_core.transaction import Transaction 5 | from plasma_core.block import Block 6 | from .exceptions import ChildChainServiceError 7 | 8 | 9 | class ChildChainService(object): 10 | 11 | def __init__(self, url): 12 | self.url = url 13 | self.methods = [func for func in dir(ChildChain) if callable(getattr(ChildChain, func)) and not func.startswith("__")] 14 | 15 | def send_request(self, method, args): 16 | payload = { 17 | "method": method, 18 | "params": args, 19 | "jsonrpc": "2.0", 20 | "id": 0, 21 | } 22 | response = requests.post(self.url, json=payload).json() 23 | if 'error' in response.keys(): 24 | raise ChildChainServiceError(response["error"]) 25 | 26 | return response["result"] 27 | 28 | def apply_transaction(self, transaction): 29 | return self.send_request("apply_transaction", [rlp.encode(transaction, Transaction).hex()]) 30 | 31 | def submit_block(self, block): 32 | return self.send_request("submit_block", [rlp.encode(block, Block).hex()]) 33 | 34 | def get_transaction(self, blknum, txindex): 35 | return self.send_request("get_transaction", [blknum, txindex]) 36 | 37 | def get_current_block(self): 38 | return self.send_request("get_current_block", []) 39 | 40 | def get_block(self, blknum): 41 | return self.send_request("get_block", [blknum]) 42 | 43 | def get_current_block_num(self): 44 | return self.send_request("get_current_block_num", []) 45 | -------------------------------------------------------------------------------- /plasma/client/client.py: -------------------------------------------------------------------------------- 1 | import rlp 2 | from ethereum import utils 3 | from web3 import HTTPProvider 4 | from plasma_core.block import Block 5 | from plasma_core.transaction import Transaction, UnsignedTransaction 6 | from plasma_core.constants import NULL_ADDRESS, CONTRACT_ADDRESS 7 | from plasma_core.utils.transactions import encode_utxo_id 8 | from plasma.root_chain.deployer import Deployer 9 | from .child_chain_service import ChildChainService 10 | from eth_utils import address 11 | 12 | 13 | class Client(object): 14 | 15 | def __init__(self, root_chain_provider=HTTPProvider('http://localhost:8545'), child_chain_url="http://localhost:8546/jsonrpc"): 16 | deployer = Deployer(root_chain_provider) 17 | self.root_chain = deployer.get_contract_at_address("RootChain", CONTRACT_ADDRESS, concise=True) 18 | self.child_chain = ChildChainService(child_chain_url) 19 | 20 | def create_transaction(self, blknum1=0, txindex1=0, oindex1=0, 21 | blknum2=0, txindex2=0, oindex2=0, 22 | newowner1=NULL_ADDRESS, amount1=0, 23 | newowner2=NULL_ADDRESS, amount2=0, 24 | cur12=NULL_ADDRESS, 25 | fee=0): 26 | return Transaction(blknum1, txindex1, oindex1, 27 | blknum2, txindex2, oindex2, 28 | cur12, 29 | newowner1, amount1, 30 | newowner2, amount2, 31 | fee) 32 | 33 | def sign_transaction(self, transaction, key1=b'', key2=b''): 34 | if key1: 35 | transaction.sign1(key1) 36 | if key2: 37 | transaction.sign2(key2) 38 | return transaction 39 | 40 | def deposit(self, amount, owner): 41 | self.root_chain.deposit(transact={'from': owner, 'value': amount}) 42 | 43 | def apply_transaction(self, transaction): 44 | self.child_chain.apply_transaction(transaction) 45 | 46 | def submit_block(self, block): 47 | self.child_chain.submit_block(block) 48 | 49 | def withdraw(self, blknum, txindex, oindex, tx, proof, sigs): 50 | utxo_pos = encode_utxo_id(blknum, txindex, oindex) 51 | encoded_transaction = rlp.encode(tx, UnsignedTransaction) 52 | owner = tx.newowner1 if oindex == 0 else tx.newowner2 53 | owner_addr = address.to_checksum_address('0x' + owner.hex()) 54 | self.root_chain.startExit(utxo_pos, encoded_transaction, proof, sigs, transact={'from': owner_addr}) 55 | 56 | def withdraw_deposit(self, owner, deposit_pos, amount): 57 | self.root_chain.startDepositExit(deposit_pos, NULL_ADDRESS, amount, transact={'from': owner}) 58 | 59 | def get_transaction(self, blknum, txindex): 60 | encoded_transaction = self.child_chain.get_transaction(blknum, txindex) 61 | return rlp.decode(utils.decode_hex(encoded_transaction), Transaction) 62 | 63 | def get_current_block(self): 64 | encoded_block = self.child_chain.get_current_block() 65 | return rlp.decode(utils.decode_hex(encoded_block), Block) 66 | 67 | def get_block(self, blknum): 68 | encoded_block = self.child_chain.get_block(blknum) 69 | return rlp.decode(utils.decode_hex(encoded_block), Block) 70 | 71 | def get_current_block_num(self): 72 | return self.child_chain.get_current_block_num() 73 | 74 | def finalize_exits(self, account): 75 | self.root_chain.finalizeExits(NULL_ADDRESS, transact={'from': account}) 76 | 77 | def challenge_exit(self, blknum, txindex, oindex, confirm_sig, account): 78 | block = self.get_block(blknum) 79 | tx = block.transaction_set[txindex] 80 | 81 | utxo_pos = encode_utxo_id(blknum, txindex, oindex) 82 | proof = block.merkle.create_membership_proof(tx.merkle_hash) 83 | sigs = tx.sig1 + tx.sig2 84 | 85 | return self.root_chain.challengeExit(utxo_pos, oindex, tx.encoded, proof, sigs, confirm_sig, transact={'from': account}) 86 | -------------------------------------------------------------------------------- /plasma/client/exceptions.py: -------------------------------------------------------------------------------- 1 | class ChildChainServiceError(Exception): 2 | """the request sent to the child chain server returned some error""" 3 | -------------------------------------------------------------------------------- /plasma/root_chain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma/root_chain/__init__.py -------------------------------------------------------------------------------- /plasma/root_chain/contracts/ByteUtils.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title Bytes operations 6 | * @dev Based on https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol. 7 | */ 8 | library ByteUtils { 9 | /* 10 | * Internal functions 11 | */ 12 | 13 | /** 14 | * @dev Slices off bytes from a byte string. 15 | * @param _bytes Byte string to slice. 16 | * @param _start Starting index of the slice. 17 | * @param _length Length of the slice. 18 | * @return The slice of the byte string. 19 | */ 20 | function slice(bytes memory _bytes, uint _start, uint _length) internal pure returns (bytes memory) { 21 | require(_bytes.length >= (_start + _length), "Slice length cannot be longer than _bytes length"); 22 | 23 | bytes memory tempBytes; 24 | 25 | assembly { 26 | switch iszero(_length) 27 | case 0 { 28 | tempBytes := mload(0x40) 29 | 30 | let lengthmod := and(_length, 31) 31 | 32 | let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) 33 | let end := add(mc, _length) 34 | 35 | for { 36 | let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) 37 | } lt(mc, end) { 38 | mc := add(mc, 0x20) 39 | cc := add(cc, 0x20) 40 | } { 41 | mstore(mc, mload(cc)) 42 | } 43 | 44 | mstore(tempBytes, _length) 45 | 46 | mstore(0x40, and(add(mc, 31), not(31))) 47 | } 48 | default { 49 | tempBytes := mload(0x40) 50 | 51 | mstore(0x40, add(tempBytes, 0x20)) 52 | } 53 | } 54 | 55 | return tempBytes; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/ECRecovery.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title Eliptic curve signature operations 6 | * @dev Based on https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d. 7 | */ 8 | library ECRecovery { 9 | /* 10 | * Internal functions 11 | */ 12 | 13 | /** 14 | * @dev Recover signer address from a message by using their signature. 15 | * @param _hash Hash of the signed message 16 | * @param _sig Signature over the signed message. 17 | * @return Address that signed the hash. 18 | */ 19 | function recover(bytes32 _hash, bytes memory _sig) internal pure returns (address) { 20 | bytes32 r; 21 | bytes32 s; 22 | uint8 v; 23 | 24 | // Check the signature length. 25 | if (_sig.length != 65) { 26 | revert("Invalid signature length."); 27 | } 28 | 29 | // Divide the signature in v, r, and s variables. 30 | assembly { 31 | r := mload(add(_sig, 32)) 32 | s := mload(add(_sig, 64)) 33 | v := byte(0, mload(add(_sig, 96))) 34 | } 35 | 36 | // Version of signature should be 27 or 28, but 0 and 1 are also possible versions. 37 | if (v < 27) { 38 | v += 27; 39 | } 40 | 41 | // If the version is correct return the signer address. 42 | if (v != 27 && v != 28) { 43 | revert("Invalid signature version."); 44 | } else { 45 | return ecrecover(_hash, v, r, s); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/Math.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title Math 6 | * @dev Basic math operations. 7 | */ 8 | library Math { 9 | /* 10 | * Internal functions. 11 | */ 12 | 13 | /** 14 | * @dev Returns the maximum of two numbers. 15 | * @param _a uint256 number. 16 | * @param _b uint256 number. 17 | * @return The greater of _a or _b. 18 | */ 19 | function max(uint256 _a, uint256 _b) internal pure returns (uint256) { 20 | return _a >= _b ? _a : _b; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/Merkle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title Merkle 6 | * @dev Operations on Merkle trees. 7 | */ 8 | library Merkle { 9 | /* 10 | * Internal function 11 | */ 12 | 13 | /** 14 | * @dev Checks that a leaf is actually in a Merkle tree. 15 | * @param _leaf Leaf to verify. 16 | * @param _index Index of the leaf in the tree. 17 | * @param _rootHash Root of the tree. 18 | * @param _proof Merkle proof showing the leaf is in the tree. 19 | * @return True if the leaf is in the tree, false otherwise. 20 | */ 21 | function checkMembership( 22 | bytes32 _leaf, 23 | uint256 _index, 24 | bytes32 _rootHash, 25 | bytes memory _proof 26 | ) internal pure returns (bool) { 27 | // Check that the proof length is valid. 28 | require(_proof.length % 32 == 0, "Invalid proof length."); 29 | 30 | // Compute the merkle root. 31 | bytes32 proofElement; 32 | bytes32 computedHash = _leaf; 33 | uint256 index = _index; 34 | for (uint256 i = 32; i <= _proof.length; i += 32) { 35 | assembly { 36 | proofElement := mload(add(_proof, i)) 37 | } 38 | if (_index % 2 == 0) { 39 | computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); 40 | } else { 41 | computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); 42 | } 43 | index = index / 2; 44 | } 45 | 46 | // Check that the computer root and specified root match. 47 | return computedHash == _rootHash; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/PlasmaRLP.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./RLPDecode.sol"; 4 | 5 | 6 | library PlasmaRLP { 7 | 8 | struct exitingTx { 9 | address payable exitor; 10 | address token; 11 | uint256 amount; 12 | uint256 inputCount; 13 | } 14 | 15 | /* Public Functions */ 16 | 17 | function getUtxoPos(bytes memory challengingTxBytes, uint256 oIndex) 18 | internal 19 | returns (uint256) 20 | { 21 | RLPDecode.RLPItem[] memory txList = RLPDecode.toList(RLPDecode.toRlpItem(challengingTxBytes)); 22 | uint256 oIndexShift = oIndex * 3; 23 | return 24 | RLPDecode.toUint(txList[0 + oIndexShift]) * 1000000000 + 25 | RLPDecode.toUint(txList[1 + oIndexShift]) * 10000 + 26 | RLPDecode.toUint(txList[2 + oIndexShift]); 27 | } 28 | 29 | function createExitingTx(bytes memory exitingTxBytes, uint256 oindex) 30 | internal 31 | returns (exitingTx memory) 32 | { 33 | RLPDecode.RLPItem[] memory txList = RLPDecode.toList(RLPDecode.toRlpItem(exitingTxBytes)); 34 | return exitingTx({ 35 | exitor: RLPDecode.toAddress(txList[7 + 2 * oindex]), 36 | token: RLPDecode.toAddress(txList[6]), 37 | amount: RLPDecode.toUint(txList[8 + 2 * oindex]), 38 | inputCount: RLPDecode.toUint(txList[0]) * RLPDecode.toUint(txList[3]) 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/PriorityQueue.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./SafeMath.sol"; 4 | 5 | 6 | /** 7 | * @title PriorityQueue 8 | * @dev A priority queue implementation. 9 | */ 10 | contract PriorityQueue { 11 | using SafeMath for uint256; 12 | 13 | /* 14 | * Storage 15 | */ 16 | 17 | address owner; 18 | uint256[] heapList; 19 | uint256 public currentSize; 20 | 21 | 22 | /* 23 | * Modifiers 24 | */ 25 | 26 | modifier onlyOwner() { 27 | require(msg.sender == owner, "Sender must be owner."); 28 | _; 29 | } 30 | 31 | 32 | /* 33 | * Constructor 34 | */ 35 | 36 | constructor() public { 37 | owner = msg.sender; 38 | heapList = [0]; 39 | currentSize = 0; 40 | } 41 | 42 | 43 | /* 44 | * Internal functions 45 | */ 46 | 47 | /** 48 | * @dev Inserts an element into the priority queue. 49 | * @param _priority Priority to insert. 50 | * @param _value Some additional value. 51 | */ 52 | function insert(uint256 _priority, uint256 _value) public onlyOwner { 53 | uint256 element = _priority << 128 | _value; 54 | heapList.push(element); 55 | currentSize = currentSize.add(1); 56 | _percUp(currentSize); 57 | } 58 | 59 | /** 60 | * @dev Returns the top element of the heap. 61 | * @return The smallest element in the priority queue. 62 | */ 63 | function getMin() public view returns (uint256, uint256) { 64 | return _splitElement(heapList[1]); 65 | } 66 | 67 | /** 68 | * @dev Deletes the top element of the heap and shifts everything up. 69 | * @return The smallest element in the priorty queue. 70 | */ 71 | function delMin() public onlyOwner returns (uint256, uint256) { 72 | uint256 retVal = heapList[1]; 73 | heapList[1] = heapList[currentSize]; 74 | delete heapList[currentSize]; 75 | currentSize = currentSize.sub(1); 76 | _percDown(1); 77 | heapList.length = heapList.length.sub(1); 78 | return _splitElement(retVal); 79 | } 80 | 81 | 82 | /* 83 | * Private functions 84 | */ 85 | 86 | /** 87 | * @dev Determines the minimum child of a given node in the tree. 88 | * @param _index Index of the node in the tree. 89 | * @return The smallest child node. 90 | */ 91 | function _minChild(uint256 _index) private view returns (uint256) { 92 | if (_index.mul(2).add(1) > currentSize) { 93 | return _index.mul(2); 94 | } else { 95 | if (heapList[_index.mul(2)] < heapList[_index.mul(2).add(1)]) { 96 | return _index.mul(2); 97 | } else { 98 | return _index.mul(2).add(1); 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * @dev Bubbles the element at some index up. 105 | */ 106 | function _percUp(uint256 _index) private { 107 | uint256 index = _index; 108 | uint256 j = index; 109 | uint256 newVal = heapList[index]; 110 | while (newVal < heapList[index.div(2)]) { 111 | heapList[index] = heapList[index.div(2)]; 112 | index = index.div(2); 113 | } 114 | if (index != j) heapList[index] = newVal; 115 | } 116 | 117 | /** 118 | * @dev Bubbles the element at some index down. 119 | */ 120 | function _percDown(uint256 _index) private { 121 | uint256 index = _index; 122 | uint256 j = index; 123 | uint256 newVal = heapList[index]; 124 | uint256 mc = _minChild(index); 125 | while (mc <= currentSize && newVal > heapList[mc]) { 126 | heapList[index] = heapList[mc]; 127 | index = mc; 128 | mc = _minChild(index); 129 | } 130 | if (index != j) heapList[index] = newVal; 131 | } 132 | 133 | /** 134 | * @dev Split an element into its priority and value. 135 | * @param _element Element to decode. 136 | * @return A tuple containing the priority and value. 137 | */ 138 | function _splitElement(uint256 _element) private pure returns (uint256, uint256) { 139 | uint256 priority = _element >> 128; 140 | uint256 value = uint256(uint128(_element)); 141 | return (priority, value); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/RLPDecode.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @author Hamdi Allam hamdi.allam97@gmail.com 6 | * Please reach our for any questions/concerns 7 | */ 8 | library RLPDecode { 9 | uint8 constant STRING_SHORT_START = 0x80; 10 | uint8 constant STRING_LONG_START = 0xb8; 11 | uint8 constant LIST_SHORT_START = 0xc0; 12 | uint8 constant LIST_LONG_START = 0xf8; 13 | 14 | uint8 constant WORD_SIZE = 32; 15 | 16 | struct RLPItem { 17 | uint len; 18 | uint memPtr; 19 | } 20 | 21 | /* 22 | * @param item RLP encoded bytes 23 | */ 24 | function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) { 25 | if (item.length == 0) 26 | return RLPItem(0, 0); 27 | 28 | uint memPtr; 29 | assembly { 30 | memPtr := add(item, 0x20) 31 | } 32 | 33 | return RLPItem(item.length, memPtr); 34 | } 35 | 36 | /* 37 | * @param item RLP encoded list in bytes 38 | */ 39 | function toList(RLPItem memory item) internal pure returns (RLPItem[] memory result) { 40 | require(isList(item), "Item must be a list."); 41 | 42 | uint items = numItems(item); 43 | result = new RLPItem[](items); 44 | 45 | uint memPtr = item.memPtr + _payloadOffset(item.memPtr); 46 | uint dataLen; 47 | for (uint i = 0; i < items; i++) { 48 | dataLen = _itemLength(memPtr); 49 | result[i] = RLPItem(dataLen, memPtr); 50 | memPtr = memPtr + dataLen; 51 | } 52 | } 53 | 54 | /* 55 | * Helpers 56 | */ 57 | 58 | // @return indicator whether encoded payload is a list. negate this function call for isData. 59 | function isList(RLPItem memory item) internal pure returns (bool) { 60 | uint8 byte0; 61 | uint memPtr = item.memPtr; 62 | assembly { 63 | byte0 := byte(0, mload(memPtr)) 64 | } 65 | 66 | if (byte0 < LIST_SHORT_START) 67 | return false; 68 | return true; 69 | } 70 | 71 | // @return number of payload items inside an encoded list. 72 | function numItems(RLPItem memory item) internal pure returns (uint) { 73 | uint count = 0; 74 | uint currPtr = item.memPtr + _payloadOffset(item.memPtr); 75 | uint endPtr = item.memPtr + item.len; 76 | while (currPtr < endPtr) { 77 | currPtr = currPtr + _itemLength(currPtr); // skip over an item 78 | count++; 79 | } 80 | 81 | return count; 82 | } 83 | 84 | // @return entire rlp item byte length 85 | function _itemLength(uint memPtr) internal pure returns (uint len) { 86 | uint byte0; 87 | assembly { 88 | byte0 := byte(0, mload(memPtr)) 89 | } 90 | 91 | if (byte0 < STRING_SHORT_START) 92 | return 1; 93 | 94 | else if (byte0 < STRING_LONG_START) 95 | return byte0 - STRING_SHORT_START + 1; 96 | 97 | else if (byte0 < LIST_SHORT_START) { 98 | assembly { 99 | let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is 100 | memPtr := add(memPtr, 1) // skip over the first byte 101 | 102 | /* 32 byte word size */ 103 | let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len 104 | len := add(dataLen, add(byteLen, 1)) 105 | } 106 | } 107 | 108 | else if (byte0 < LIST_LONG_START) { 109 | return byte0 - LIST_SHORT_START + 1; 110 | } 111 | 112 | else { 113 | assembly { 114 | let byteLen := sub(byte0, 0xf7) 115 | memPtr := add(memPtr, 1) 116 | 117 | let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length 118 | len := add(dataLen, add(byteLen, 1)) 119 | } 120 | } 121 | } 122 | 123 | // @return number of bytes until the data 124 | function _payloadOffset(uint memPtr) internal pure returns (uint) { 125 | uint byte0; 126 | assembly { 127 | byte0 := byte(0, mload(memPtr)) 128 | } 129 | 130 | if (byte0 < STRING_SHORT_START) 131 | return 0; 132 | else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) 133 | return 1; 134 | else if (byte0 < LIST_SHORT_START) // being explicit 135 | return byte0 - (STRING_LONG_START - 1) + 1; 136 | else 137 | return byte0 - (LIST_LONG_START - 1) + 1; 138 | } 139 | 140 | /** RLPItem conversions into data types **/ 141 | 142 | function toBoolean(RLPItem memory item) internal pure returns (bool) { 143 | require(item.len == 1, "Item must be a boolean."); 144 | uint result; 145 | uint memPtr = item.memPtr; 146 | assembly { 147 | result := byte(0, mload(memPtr)) 148 | } 149 | 150 | return result == 0 ? false : true; 151 | } 152 | 153 | function toAddress(RLPItem memory item) internal pure returns (address payable) { 154 | // 1 byte for the length prefix according to RLP spec 155 | require(item.len == 21, "Item must be 21 characters long."); 156 | 157 | uint memPtr = item.memPtr + 1; // skip the length prefix 158 | uint addr; 159 | assembly { 160 | addr := div(mload(memPtr), exp(256, 12)) // right shift 12 bytes. we want the most significant 20 bytes 161 | } 162 | 163 | return address(addr); 164 | } 165 | 166 | function toUint(RLPItem memory item) internal pure returns (uint) { 167 | uint offset = _payloadOffset(item.memPtr); 168 | uint len = item.len - offset; 169 | uint memPtr = item.memPtr + offset; 170 | 171 | uint result; 172 | assembly { 173 | result := div(mload(memPtr), exp(256, sub(32, len))) // shift to the correct location 174 | } 175 | 176 | return result; 177 | } 178 | 179 | function toBytes(RLPItem memory item) internal pure returns (bytes memory) { 180 | uint offset = _payloadOffset(item.memPtr); 181 | uint len = item.len - offset; // data length 182 | bytes memory result = new bytes(len); 183 | 184 | uint destPtr; 185 | assembly { 186 | destPtr := add(0x20, result) 187 | } 188 | 189 | copy(item.memPtr + offset, destPtr, len); 190 | return result; 191 | } 192 | 193 | 194 | /* 195 | * @param src Pointer to source 196 | * @param dest Pointer to destination 197 | * @param len Amount of memory to copy from the source 198 | */ 199 | function copy(uint src, uint dest, uint len) internal pure { 200 | // copy as many word sizes as possible 201 | for (; len >= WORD_SIZE; len -= WORD_SIZE) { 202 | assembly { 203 | mstore(dest, mload(dest)) 204 | } 205 | 206 | src += WORD_SIZE; 207 | dest += WORD_SIZE; 208 | } 209 | 210 | // left over bytes 211 | uint mask = 256 ** (WORD_SIZE - len) - 1; 212 | assembly { 213 | let srcpart := and(mload(src), not(mask)) // zero out src 214 | let destpart := and(mload(dest), mask) // retrieve the bytes 215 | mstore(dest, or(destpart, srcpart)) 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/RLPEncode.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title RLPEncode 6 | * @dev A simple RLP encoding library. 7 | * @author Bakaoh 8 | */ 9 | library RLPEncode { 10 | /* 11 | * Internal functions 12 | */ 13 | 14 | /** 15 | * @dev RLP encodes a byte string. 16 | * @param self The byte string to encode. 17 | * @return The RLP encoded string in bytes. 18 | */ 19 | function encodeBytes(bytes memory self) internal pure returns (bytes memory) { 20 | bytes memory encoded; 21 | if (self.length == 1 && uint8(self[0]) <= 128) { 22 | encoded = self; 23 | } else { 24 | encoded = concat(encodeLength(self.length, 128), self); 25 | } 26 | return encoded; 27 | } 28 | 29 | /** 30 | * @dev RLP encodes a list of RLP encoded byte byte strings. 31 | * @param self The list of RLP encoded byte strings. 32 | * @return The RLP encoded list of items in bytes. 33 | */ 34 | function encodeList(bytes[] memory self) internal pure returns (bytes memory) { 35 | bytes memory list = flatten(self); 36 | return concat(encodeLength(list.length, 192), list); 37 | } 38 | 39 | /** 40 | * @dev RLP encodes a string. 41 | * @param self The string to encode. 42 | * @return The RLP encoded string in bytes. 43 | */ 44 | function encodeString(string memory self) internal pure returns (bytes memory) { 45 | return encodeBytes(bytes(self)); 46 | } 47 | 48 | /** 49 | * @dev RLP encodes an address. 50 | * @param self The address to encode. 51 | * @return The RLP encoded address in bytes. 52 | */ 53 | function encodeAddress(address self) internal pure returns (bytes memory) { 54 | bytes memory inputBytes; 55 | assembly { 56 | let m := mload(0x40) 57 | mstore(add(m, 20), xor(0x140000000000000000000000000000000000000000, self)) 58 | mstore(0x40, add(m, 52)) 59 | inputBytes := m 60 | } 61 | return encodeBytes(inputBytes); 62 | } 63 | 64 | /** 65 | * @dev RLP encodes a uint. 66 | * @param self The uint to encode. 67 | * @return The RLP encoded uint in bytes. 68 | */ 69 | function encodeUint(uint self) internal pure returns (bytes memory) { 70 | return encodeBytes(toBinary(self)); 71 | } 72 | 73 | /** 74 | * @dev RLP encodes an int. 75 | * @param self The int to encode. 76 | * @return The RLP encoded int in bytes. 77 | */ 78 | function encodeInt(int self) internal pure returns (bytes memory) { 79 | return encodeUint(uint(self)); 80 | } 81 | 82 | /** 83 | * @dev RLP encodes a bool. 84 | * @param self The bool to encode. 85 | * @return The RLP encoded bool in bytes. 86 | */ 87 | function encodeBool(bool self) internal pure returns (bytes memory) { 88 | bytes memory encoded = new bytes(1); 89 | encoded[0] = (self ? bytes1(0x01) : bytes1(0x80)); 90 | return encoded; 91 | } 92 | 93 | 94 | /* 95 | * Private functions 96 | */ 97 | 98 | /** 99 | * @dev Encode the first byte, followed by the `len` in binary form if `length` is more than 55. 100 | * @param len The length of the string or the payload. 101 | * @param offset 128 if item is string, 192 if item is list. 102 | * @return RLP encoded bytes. 103 | */ 104 | function encodeLength(uint len, uint offset) private pure returns (bytes memory) { 105 | bytes memory encoded; 106 | if (len < 56) { 107 | encoded = new bytes(1); 108 | encoded[0] = byte(uint8(len) + uint8(offset)); 109 | } else { 110 | uint lenLen; 111 | uint i = 1; 112 | while (len / i != 0) { 113 | lenLen++; 114 | i *= 256; 115 | } 116 | 117 | encoded = new bytes(lenLen + 1); 118 | encoded[0] = byte(uint8(lenLen) + uint8(offset) + 55); 119 | for(i = 1; i <= lenLen; i++) { 120 | encoded[i] = byte(uint8((len / (256**(lenLen-i))) % 256)); 121 | } 122 | } 123 | return encoded; 124 | } 125 | 126 | /** 127 | * @dev Encode integer in big endian binary form with no leading zeroes. 128 | * @notice TODO: This should be optimized with assembly to save gas costs. 129 | * @param _x The integer to encode. 130 | * @return RLP encoded bytes. 131 | */ 132 | function toBinary(uint _x) private pure returns (bytes memory) { 133 | bytes memory b = new bytes(32); 134 | assembly { 135 | mstore(add(b, 32), _x) 136 | } 137 | uint i = 0; 138 | for (; i < 32; i++) { 139 | if (b[i] != 0) { 140 | break; 141 | } 142 | } 143 | bytes memory res = new bytes(32 - i); 144 | for (uint j = 0; j < res.length; j++) { 145 | res[j] = b[i++]; 146 | } 147 | return res; 148 | } 149 | 150 | /** 151 | * @dev Copies a piece of memory to another location. 152 | * @notice From: https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol. 153 | * @param _dest Destination location. 154 | * @param _src Source location. 155 | * @param _len Length of memory to copy. 156 | */ 157 | function memcpy(uint _dest, uint _src, uint _len) private pure { 158 | uint dest = _dest; 159 | uint src = _src; 160 | uint len = _len; 161 | 162 | for(; len >= 32; len -= 32) { 163 | assembly { 164 | mstore(dest, mload(src)) 165 | } 166 | dest += 32; 167 | src += 32; 168 | } 169 | 170 | uint mask = 256 ** (32 - len) - 1; 171 | assembly { 172 | let srcpart := and(mload(src), not(mask)) 173 | let destpart := and(mload(dest), mask) 174 | mstore(dest, or(destpart, srcpart)) 175 | } 176 | } 177 | 178 | /** 179 | * @dev Flattens a list of byte strings into one byte string. 180 | * @notice From: https://github.com/sammayo/solidity-rlp-encoder/blob/master/RLPEncode.sol. 181 | * @param _list List of byte strings to flatten. 182 | * @return The flattened byte string. 183 | */ 184 | function flatten(bytes[] memory _list) private pure returns (bytes memory) { 185 | if (_list.length == 0) { 186 | return new bytes(0); 187 | } 188 | 189 | uint len; 190 | uint i = 0; 191 | for (; i < _list.length; i++) { 192 | len += _list[i].length; 193 | } 194 | 195 | bytes memory flattened = new bytes(len); 196 | uint flattenedPtr; 197 | assembly { flattenedPtr := add(flattened, 0x20) } 198 | 199 | for(i = 0; i < _list.length; i++) { 200 | bytes memory item = _list[i]; 201 | 202 | uint listPtr; 203 | assembly { listPtr := add(item, 0x20)} 204 | 205 | memcpy(flattenedPtr, listPtr, item.length); 206 | flattenedPtr += _list[i].length; 207 | } 208 | 209 | return flattened; 210 | } 211 | 212 | /** 213 | * @dev Concatenates two bytes. 214 | * @notice From: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol. 215 | * @param _preBytes First byte string. 216 | * @param _postBytes Second byte string. 217 | * @return Both byte string combined. 218 | */ 219 | function concat(bytes memory _preBytes, bytes memory _postBytes) private pure returns (bytes memory) { 220 | bytes memory tempBytes; 221 | 222 | assembly { 223 | tempBytes := mload(0x40) 224 | 225 | let length := mload(_preBytes) 226 | mstore(tempBytes, length) 227 | 228 | let mc := add(tempBytes, 0x20) 229 | let end := add(mc, length) 230 | 231 | for { 232 | let cc := add(_preBytes, 0x20) 233 | } lt(mc, end) { 234 | mc := add(mc, 0x20) 235 | cc := add(cc, 0x20) 236 | } { 237 | mstore(mc, mload(cc)) 238 | } 239 | 240 | length := mload(_postBytes) 241 | mstore(tempBytes, add(length, mload(tempBytes))) 242 | 243 | mc := end 244 | end := add(mc, length) 245 | 246 | for { 247 | let cc := add(_postBytes, 0x20) 248 | } lt(mc, end) { 249 | mc := add(mc, 0x20) 250 | cc := add(cc, 0x20) 251 | } { 252 | mstore(mc, mload(cc)) 253 | } 254 | 255 | mstore(0x40, and( 256 | add(add(end, iszero(add(length, mload(_preBytes)))), 31), 257 | not(31) 258 | )) 259 | } 260 | 261 | return tempBytes; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/RootChain.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./SafeMath.sol"; 4 | import "./Math.sol"; 5 | import "./PlasmaRLP.sol"; 6 | import "./Merkle.sol"; 7 | import "./Validate.sol"; 8 | import "./PriorityQueue.sol"; 9 | 10 | 11 | /** 12 | * @title RootChain 13 | * @dev This contract secures a utxo payments plasma child chain to ethereum. 14 | */ 15 | contract RootChain { 16 | using SafeMath for uint256; 17 | using Merkle for bytes32; 18 | using PlasmaRLP for bytes; 19 | 20 | 21 | /* 22 | * Events 23 | */ 24 | 25 | event Deposit( 26 | address indexed depositor, 27 | uint256 indexed depositBlock, 28 | address token, 29 | uint256 amount 30 | ); 31 | 32 | event ExitStarted( 33 | address indexed exitor, 34 | uint256 indexed utxoPos, 35 | address token, 36 | uint256 amount 37 | ); 38 | 39 | event BlockSubmitted( 40 | bytes32 root, 41 | uint256 timestamp 42 | ); 43 | 44 | event TokenAdded( 45 | address token 46 | ); 47 | 48 | 49 | /* 50 | * Storage 51 | */ 52 | 53 | uint256 public constant EXIT_BOND = 1234567890; 54 | uint256 public constant CHILD_BLOCK_INTERVAL = 1000; 55 | 56 | address public operator; 57 | 58 | uint256 public currentChildBlock; 59 | uint256 public currentDepositBlock; 60 | uint256 public currentFeeExit; 61 | 62 | mapping (uint256 => PlasmaBlock) public plasmaBlocks; 63 | mapping (uint256 => Exit) public exits; 64 | mapping (address => address) public exitsQueues; 65 | 66 | struct Exit { 67 | address payable owner; 68 | address token; 69 | uint256 amount; 70 | } 71 | 72 | struct PlasmaBlock { 73 | bytes32 root; 74 | uint256 timestamp; 75 | } 76 | 77 | 78 | /* 79 | * Modifiers 80 | */ 81 | 82 | modifier onlyOperator() { 83 | require(msg.sender == operator, "Sender must be operator."); 84 | _; 85 | } 86 | 87 | modifier onlyWithValue(uint256 _value) { 88 | require(msg.value == _value, "Invalid attached value."); 89 | _; 90 | } 91 | 92 | 93 | /* 94 | * Constructor 95 | */ 96 | 97 | constructor() public { 98 | operator = msg.sender; 99 | currentChildBlock = CHILD_BLOCK_INTERVAL; 100 | currentDepositBlock = 1; 101 | currentFeeExit = 1; 102 | // Support only ETH on deployment; other tokens need 103 | // to be added explicitly. 104 | exitsQueues[address(0)] = address(new PriorityQueue()); 105 | } 106 | 107 | 108 | /* 109 | * Public Functions 110 | */ 111 | 112 | /** 113 | * @dev Allows Plasma chain operator to submit block root. 114 | * @param _root The root of a child chain block. 115 | */ 116 | function submitBlock(bytes32 _root) public onlyOperator { 117 | plasmaBlocks[currentChildBlock] = PlasmaBlock({ 118 | root: _root, 119 | timestamp: block.timestamp 120 | }); 121 | 122 | // Update block numbers. 123 | currentChildBlock = currentChildBlock.add(CHILD_BLOCK_INTERVAL); 124 | currentDepositBlock = 1; 125 | 126 | emit BlockSubmitted(_root, block.timestamp); 127 | } 128 | 129 | /** 130 | * @dev Allows anyone to deposit funds into the Plasma chain. 131 | */ 132 | function deposit() public payable { 133 | // Only allow up to CHILD_BLOCK_INTERVAL deposits per child block. 134 | require(currentDepositBlock < CHILD_BLOCK_INTERVAL, "Deposit limit reached."); 135 | 136 | bytes32 root = keccak256(abi.encodePacked(msg.sender, address(0), msg.value)); 137 | uint256 depositBlock = getDepositBlock(); 138 | plasmaBlocks[depositBlock] = PlasmaBlock({ 139 | root: root, 140 | timestamp: block.timestamp 141 | }); 142 | currentDepositBlock = currentDepositBlock.add(1); 143 | 144 | emit Deposit(msg.sender, depositBlock, address(0), msg.value); 145 | } 146 | 147 | /** 148 | * @dev Starts an exit from a deposit. 149 | * @param _depositPos UTXO position of the deposit. 150 | * @param _token Token type to deposit. 151 | * @param _amount Deposit amount. 152 | */ 153 | function startDepositExit( 154 | uint256 _depositPos, 155 | address _token, 156 | uint256 _amount 157 | ) 158 | public payable onlyWithValue(EXIT_BOND) 159 | { 160 | uint256 blknum = _depositPos / 1000000000; 161 | 162 | // Check that the given UTXO is a deposit. 163 | require(blknum % CHILD_BLOCK_INTERVAL != 0, "Referenced block must be a deposit block."); 164 | 165 | // Validate the given owner and amount. 166 | bytes32 root = plasmaBlocks[blknum].root; 167 | bytes32 depositHash = keccak256(abi.encodePacked(msg.sender, _token, _amount)); 168 | require(root == depositHash, "Root hash must match deposit hash."); 169 | 170 | addExitToQueue(_depositPos, msg.sender, _token, _amount, plasmaBlocks[blknum].timestamp); 171 | } 172 | 173 | /** 174 | * @dev Allows the operator withdraw any allotted fees. Starts an exit to avoid theft. 175 | * @param _token Token to withdraw. 176 | * @param _amount Amount in fees to withdraw. 177 | */ 178 | function startFeeExit(address _token, uint256 _amount) public payable onlyOperator onlyWithValue(EXIT_BOND) { 179 | addExitToQueue(currentFeeExit, msg.sender, _token, _amount, block.timestamp + 1); 180 | currentFeeExit = currentFeeExit.add(1); 181 | } 182 | 183 | /** 184 | * @dev Starts to exit a specified utxo. 185 | * @param _utxoPos The position of the exiting utxo in the format of blknum * 1000000000 + index * 10000 + oindex. 186 | * @param _txBytes The transaction being exited in RLP bytes format. 187 | * @param _proof Proof of the exiting transactions inclusion for the block specified by utxoPos. 188 | * @param _sigs Both transaction signatures and confirmations signatures used to verify that the exiting transaction has been confirmed. 189 | */ 190 | function startExit( 191 | uint256 _utxoPos, 192 | bytes memory _txBytes, 193 | bytes memory _proof, 194 | bytes memory _sigs 195 | ) 196 | public payable onlyWithValue(EXIT_BOND) 197 | { 198 | uint256 blknum = _utxoPos / 1000000000; 199 | uint256 txindex = (_utxoPos % 1000000000) / 10000; 200 | uint256 oindex = _utxoPos - blknum * 1000000000 - txindex * 10000; 201 | 202 | // Check the sender owns this UTXO. 203 | PlasmaRLP.exitingTx memory exitingTx = _txBytes.createExitingTx(oindex); 204 | require(msg.sender == exitingTx.exitor, "Sender must be exitor."); 205 | 206 | // Check the transaction was included in the chain and is correctly signed. 207 | bytes32 root = plasmaBlocks[blknum].root; 208 | bytes32 merkleHash = keccak256(abi.encodePacked(keccak256(_txBytes), ByteUtils.slice(_sigs, 0, 130))); 209 | require(Validate.checkSigs(keccak256(_txBytes), root, exitingTx.inputCount, _sigs), "Signatures must match."); 210 | require(merkleHash.checkMembership(txindex, root, _proof), "Transaction Merkle proof is invalid."); 211 | 212 | addExitToQueue(_utxoPos, exitingTx.exitor, exitingTx.token, exitingTx.amount, plasmaBlocks[blknum].timestamp); 213 | } 214 | 215 | /** 216 | * @dev Allows anyone to challenge an exiting transaction by submitting proof of a double spend on the child chain. 217 | * @param _cUtxoPos The position of the challenging utxo. 218 | * @param _eUtxoIndex The output position of the exiting utxo. 219 | * @param _txBytes The challenging transaction in bytes RLP form. 220 | * @param _proof Proof of inclusion for the transaction used to challenge. 221 | * @param _sigs Signatures for the transaction used to challenge. 222 | * @param _confirmationSig The confirmation signature for the transaction used to challenge. 223 | */ 224 | function challengeExit( 225 | uint256 _cUtxoPos, 226 | uint256 _eUtxoIndex, 227 | bytes memory _txBytes, 228 | bytes memory _proof, 229 | bytes memory _sigs, 230 | bytes memory _confirmationSig 231 | ) 232 | public 233 | { 234 | uint256 eUtxoPos = _txBytes.getUtxoPos(_eUtxoIndex); 235 | uint256 txindex = (_cUtxoPos % 1000000000) / 10000; 236 | bytes32 root = plasmaBlocks[_cUtxoPos / 1000000000].root; 237 | bytes32 txHash = keccak256(_txBytes); 238 | bytes32 confirmationHash = keccak256(abi.encodePacked(txHash, root)); 239 | bytes32 merkleHash = keccak256(abi.encodePacked(txHash, _sigs)); 240 | address owner = exits[eUtxoPos].owner; 241 | 242 | // Validate the spending transaction. 243 | require(owner == ECRecovery.recover(confirmationHash, _confirmationSig), "Confirmation signature must be signed by owner."); 244 | require(merkleHash.checkMembership(txindex, root, _proof), "Transaction Merkle proof is invalid."); 245 | 246 | // Delete the owner but keep the amount to prevent another exit. 247 | delete exits[eUtxoPos].owner; 248 | msg.sender.transfer(EXIT_BOND); 249 | } 250 | 251 | /** 252 | * @dev Determines the next exit to be processed. 253 | * @param _token Asset type to be exited. 254 | * @return A tuple of the position and time when this exit can be processed. 255 | */ 256 | function getNextExit(address _token) public view returns (uint256, uint256) { 257 | return PriorityQueue(exitsQueues[_token]).getMin(); 258 | } 259 | 260 | /** 261 | * @dev Processes any exits that have completed the challenge period. 262 | * @param _token Token type to process. 263 | */ 264 | function finalizeExits(address _token) public { 265 | uint256 utxoPos; 266 | uint256 exitableAt; 267 | (exitableAt, utxoPos) = getNextExit(_token); 268 | PriorityQueue queue = PriorityQueue(exitsQueues[_token]); 269 | Exit memory currentExit = exits[utxoPos]; 270 | while (exitableAt < block.timestamp) { 271 | currentExit = exits[utxoPos]; 272 | 273 | // FIXME: handle ERC-20 transfer 274 | require(address(0) == _token, "Token must be ETH."); 275 | 276 | if (currentExit.owner != address(0)) { 277 | currentExit.owner.transfer(currentExit.amount + EXIT_BOND); 278 | } 279 | 280 | queue.delMin(); 281 | delete exits[utxoPos].owner; 282 | 283 | if (queue.currentSize() > 0) { 284 | (exitableAt, utxoPos) = getNextExit(_token); 285 | } else { 286 | return; 287 | } 288 | } 289 | } 290 | 291 | 292 | /* 293 | * Public view functions 294 | */ 295 | 296 | /** 297 | * @dev Queries the child chain. 298 | * @param _blockNumber Number of the block to return. 299 | * @return Child chain block at the specified block number. 300 | */ 301 | function getPlasmaBlock(uint256 _blockNumber) public view returns (bytes32, uint256) { 302 | return (plasmaBlocks[_blockNumber].root, plasmaBlocks[_blockNumber].timestamp); 303 | } 304 | 305 | /** 306 | * @dev Determines the next deposit block number. 307 | * @return Block number to be given to the next deposit block. 308 | */ 309 | function getDepositBlock() public view returns (uint256) { 310 | return currentChildBlock.sub(CHILD_BLOCK_INTERVAL).add(currentDepositBlock); 311 | } 312 | 313 | /** 314 | * @dev Returns information about an exit. 315 | * @param _utxoPos Position of the UTXO in the chain. 316 | * @return A tuple representing the active exit for the given UTXO. 317 | */ 318 | function getExit(uint256 _utxoPos) public view returns (address, address, uint256) { 319 | return (exits[_utxoPos].owner, exits[_utxoPos].token, exits[_utxoPos].amount); 320 | } 321 | 322 | 323 | /* 324 | * Private functions 325 | */ 326 | 327 | /** 328 | * @dev Adds an exit to the exit queue. 329 | * @param _utxoPos Position of the UTXO in the child chain. 330 | * @param _exitor Owner of the UTXO. 331 | * @param _token Token to be exited. 332 | * @param _amount Amount to be exited. 333 | * @param _created_at Time when the UTXO was created. 334 | */ 335 | function addExitToQueue( 336 | uint256 _utxoPos, 337 | address payable _exitor, 338 | address _token, 339 | uint256 _amount, 340 | uint256 _created_at 341 | ) 342 | private 343 | { 344 | // Check that we're exiting a known token. 345 | require(exitsQueues[_token] != address(0), "Must exit a known token."); 346 | 347 | // Check exit is valid and doesn't already exist. 348 | require(_amount > 0, "Exit value cannot be zero."); 349 | require(exits[_utxoPos].amount == 0, "Exit cannot already exist."); 350 | 351 | // Calculate priority. 352 | uint256 exitableAt = Math.max(_created_at + 2 weeks, block.timestamp + 1 weeks); 353 | PriorityQueue queue = PriorityQueue(exitsQueues[_token]); 354 | queue.insert(exitableAt, _utxoPos); 355 | 356 | exits[_utxoPos] = Exit({ 357 | owner: _exitor, 358 | token: _token, 359 | amount: _amount 360 | }); 361 | 362 | emit ExitStarted(msg.sender, _utxoPos, _token, _amount); 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error. 7 | */ 8 | library SafeMath { 9 | /* 10 | * Internal functions 11 | */ 12 | 13 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 14 | if (a == 0) { 15 | return 0; 16 | } 17 | uint256 c = a * b; 18 | assert(c / a == b); 19 | return c; 20 | } 21 | 22 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 23 | uint256 c = a / b; 24 | return c; 25 | } 26 | 27 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 28 | assert(b <= a); 29 | return a - b; 30 | } 31 | 32 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 33 | uint256 c = a + b; 34 | assert(c >= a); 35 | return c; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plasma/root_chain/contracts/Validate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./ByteUtils.sol"; 4 | import "./ECRecovery.sol"; 5 | 6 | 7 | /** 8 | * @title Validate 9 | * @dev Checks that the signatures on a transaction are valid 10 | */ 11 | library Validate { 12 | function checkSigs(bytes32 txHash, bytes32 rootHash, uint256 blknum2, bytes memory sigs) 13 | internal 14 | view 15 | returns (bool) 16 | { 17 | require(sigs.length % 65 == 0 && sigs.length <= 260, "Invalid signature length."); 18 | bytes memory sig1 = ByteUtils.slice(sigs, 0, 65); 19 | bytes memory sig2 = ByteUtils.slice(sigs, 65, 65); 20 | bytes memory confSig1 = ByteUtils.slice(sigs, 130, 65); 21 | bytes32 confirmationHash = keccak256(abi.encodePacked(txHash, rootHash)); 22 | 23 | bool check1 = true; 24 | bool check2 = true; 25 | 26 | check1 = ECRecovery.recover(txHash, sig1) == ECRecovery.recover(confirmationHash, confSig1); 27 | if (blknum2 > 0) { 28 | bytes memory confSig2 = ByteUtils.slice(sigs, 195, 65); 29 | check2 = ECRecovery.recover(txHash, sig2) == ECRecovery.recover(confirmationHash, confSig2); 30 | } 31 | return check1 && check2; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plasma/root_chain/deployer.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from solc import compile_standard 4 | from web3.contract import ConciseContract 5 | from web3 import Web3, HTTPProvider 6 | 7 | OWN_DIR = os.path.dirname(os.path.realpath(__file__)) 8 | CONTRACTS_DIR = OWN_DIR + '/contracts' 9 | OUTPUT_DIR = 'contract_data' 10 | 11 | 12 | class Deployer(object): 13 | 14 | def __init__(self, provider=HTTPProvider('http://localhost:8545')): 15 | self.w3 = Web3(provider) 16 | 17 | @staticmethod 18 | def get_solc_input(): 19 | """Walks the contract directory and returns a Solidity input dict 20 | 21 | Learn more about Solidity input JSON here: https://goo.gl/7zKBvj 22 | 23 | Returns: 24 | dict: A Solidity input JSON object as a dict 25 | """ 26 | 27 | solc_input = { 28 | 'language': 'Solidity', 29 | 'sources': { 30 | file_name: { 31 | 'urls': [os.path.realpath(os.path.join(r, file_name))] 32 | } for r, d, f in os.walk(CONTRACTS_DIR) for file_name in f 33 | }, 34 | 'settings': { 35 | 'outputSelection': { 36 | "*": { 37 | "": [ 38 | "legacyAST", 39 | "ast" 40 | ], 41 | "*": [ 42 | "abi", 43 | "evm.bytecode.object", 44 | "evm.bytecode.sourceMap", 45 | "evm.deployedBytecode.object", 46 | "evm.deployedBytecode.sourceMap" 47 | ] 48 | } 49 | } 50 | } 51 | } 52 | 53 | return solc_input 54 | 55 | def compile_all(self): 56 | """Compiles all of the contracts in the /contracts directory 57 | 58 | Creates {contract name}.json files in /build that contain 59 | the build output for each contract. 60 | """ 61 | 62 | # Solidity input JSON 63 | solc_input = self.get_solc_input() 64 | 65 | # Compile the contracts 66 | compilation_result = compile_standard(solc_input, allow_paths=CONTRACTS_DIR) 67 | 68 | # Create the output folder if it doesn't already exist 69 | os.makedirs(OUTPUT_DIR, exist_ok=True) 70 | 71 | # Write the contract ABI to output files 72 | compiled_contracts = compilation_result['contracts'] 73 | for contract_file in compiled_contracts: 74 | for contract in compiled_contracts[contract_file]: 75 | contract_name = contract.split('.')[0] 76 | contract_data = compiled_contracts[contract_file][contract_name] 77 | 78 | contract_data_path = OUTPUT_DIR + '/{0}.json'.format(contract_name) 79 | with open(contract_data_path, "w+") as contract_data_file: 80 | json.dump(contract_data, contract_data_file) 81 | 82 | @staticmethod 83 | def get_contract_data(contract_name): 84 | """Returns the contract data for a given contract 85 | 86 | Args: 87 | contract_name (str): Name of the contract to return. 88 | 89 | Returns: 90 | str, str: ABI and bytecode of the contract 91 | """ 92 | 93 | contract_data_path = OUTPUT_DIR + '/{0}.json'.format(contract_name) 94 | with open(contract_data_path, 'r') as contract_data_file: 95 | contract_data = json.load(contract_data_file) 96 | 97 | abi = contract_data['abi'] 98 | bytecode = contract_data['evm']['bytecode']['object'] 99 | 100 | return abi, bytecode 101 | 102 | def deploy_contract(self, contract_name, gas=5000000, args=(), concise=True): 103 | """Deploys a contract to the given Ethereum network using Web3 104 | 105 | Args: 106 | contract_name (str): Name of the contract to deploy. Must already be compiled. 107 | provider (HTTPProvider): The Web3 provider to deploy with. 108 | gas (int): Amount of gas to use when creating the contract. 109 | args (obj): Any additional arguments to include with the contract creation. 110 | concise (bool): Whether to return a Contract or ConciseContract instance. 111 | 112 | Returns: 113 | Contract: A Web3 contract instance. 114 | """ 115 | 116 | abi, bytecode = self.get_contract_data(contract_name) 117 | 118 | contract = self.w3.eth.contract(abi=abi, bytecode=bytecode) 119 | 120 | # Get transaction hash from deployed contract 121 | tx_hash = contract.deploy(transaction={ 122 | 'from': self.w3.eth.accounts[0], 123 | 'gas': gas 124 | }, args=args) 125 | 126 | # Get tx receipt to get contract address 127 | tx_receipt = self.w3.eth.getTransactionReceipt(tx_hash) 128 | contract_address = tx_receipt['contractAddress'] 129 | 130 | contract_instance = self.w3.eth.contract(address=contract_address, abi=abi) 131 | 132 | print("Successfully deployed {0} contract!".format(contract_name)) 133 | 134 | return ConciseContract(contract_instance) if concise else contract_instance 135 | 136 | def get_contract_at_address(self, contract_name, address, concise=True): 137 | """Returns a Web3 instance of the given contract at the given address 138 | 139 | Args: 140 | contract_name (str): Name of the contract. Must already be compiled. 141 | address (str): Address of the contract. 142 | concise (bool): Whether to return a Contract or ConciseContract instance. 143 | 144 | Returns: 145 | Contract: A Web3 contract instance. 146 | """ 147 | 148 | abi, _ = self.get_contract_data(contract_name) 149 | 150 | contract_instance = self.w3.eth.contract(abi=abi, address=address) 151 | 152 | return ConciseContract(contract_instance) if concise else contract_instance 153 | -------------------------------------------------------------------------------- /plasma_core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma_core/__init__.py -------------------------------------------------------------------------------- /plasma_core/block.py: -------------------------------------------------------------------------------- 1 | import rlp 2 | from rlp.sedes import binary, CountableList, big_endian_int 3 | from ethereum import utils 4 | from plasma_core.utils.merkle.fixed_merkle import FixedMerkle 5 | from plasma_core.utils.signatures import sign, get_signer 6 | from plasma_core.utils.transactions import encode_utxo_id 7 | from plasma_core.transaction import Transaction 8 | from plasma_core.constants import NULL_SIGNATURE 9 | 10 | 11 | class Block(rlp.Serializable): 12 | 13 | fields = [ 14 | ('transaction_set', CountableList(Transaction)), 15 | ('number', big_endian_int), 16 | ('sig', binary), 17 | ] 18 | 19 | def __init__(self, transaction_set=None, number=0, sig=NULL_SIGNATURE): 20 | self.transaction_set = transaction_set or [] 21 | self.number = number 22 | self.sig = sig 23 | self.spent_utxos = {} 24 | 25 | @property 26 | def hash(self): 27 | return utils.sha3(self.encoded) 28 | 29 | @property 30 | def signer(self): 31 | return get_signer(self.hash, self.sig) 32 | 33 | @property 34 | def merkle(self): 35 | hashed_transaction_set = [transaction.merkle_hash for transaction in self.transaction_set] 36 | return FixedMerkle(16, hashed_transaction_set, hashed=True) 37 | 38 | @property 39 | def root(self): 40 | return self.merkle.root 41 | 42 | @property 43 | def is_deposit_block(self): 44 | return len(self.transaction_set) == 1 and self.transaction_set[0].is_deposit_transaction 45 | 46 | @property 47 | def encoded(self): 48 | return rlp.encode(self, UnsignedBlock) 49 | 50 | def sign(self, key): 51 | self.sig = sign(self.hash, key) 52 | 53 | def add_transaction(self, tx): 54 | self.transaction_set.append(tx) 55 | inputs = [(tx.blknum1, tx.txindex1, tx.oindex1), (tx.blknum2, tx.txindex2, tx.oindex2)] 56 | for i in inputs: 57 | input_id = encode_utxo_id(*i) 58 | self.spent_utxos[input_id] = True 59 | 60 | 61 | UnsignedBlock = Block.exclude(['sig']) 62 | -------------------------------------------------------------------------------- /plasma_core/chain.py: -------------------------------------------------------------------------------- 1 | from plasma_core.utils.transactions import decode_utxo_id, encode_utxo_id 2 | from plasma_core.utils.address import address_to_hex 3 | from plasma_core.constants import NULL_SIGNATURE 4 | from plasma_core.exceptions import (InvalidBlockSignatureException, 5 | InvalidTxSignatureException, 6 | TxAlreadySpentException, 7 | TxAmountMismatchException) 8 | 9 | 10 | class Chain(object): 11 | 12 | def __init__(self, operator): 13 | self.operator = operator 14 | self.blocks = {} 15 | self.parent_queue = {} 16 | self.child_block_interval = 1000 17 | self.next_child_block = self.child_block_interval 18 | self.next_deposit_block = 1 19 | 20 | def add_block(self, block): 21 | # Is the block being added to the head? 22 | is_next_child_block = block.number == self.next_child_block 23 | if is_next_child_block or block.number == self.next_deposit_block: 24 | self._validate_block(block) 25 | 26 | # Insert the block into the chain. 27 | self._apply_block(block) 28 | 29 | # Update the head state. 30 | if is_next_child_block: 31 | self.next_deposit_block = self.next_child_block + 1 32 | self.next_child_block += self.child_block_interval 33 | else: 34 | self.next_deposit_block += 1 35 | # Or does the block not yet have a parent? 36 | elif block.number > self.next_deposit_block: 37 | parent_block_number = block.number - 1 38 | if parent_block_number not in self.parent_queue: 39 | self.parent_queue[parent_block_number] = [] 40 | self.parent_queue[parent_block_number].append(block) 41 | return False 42 | # Block already exists. 43 | else: 44 | return False 45 | 46 | # Process any blocks that were waiting for this block. 47 | if block.number in self.parent_queue: 48 | for blk in self.parent_queue[block.number]: 49 | self.add_block(blk) 50 | del self.parent_queue[block.number] 51 | return True 52 | 53 | def validate_transaction(self, tx, temp_spent={}): 54 | input_amount = 0 55 | output_amount = tx.amount1 + tx.amount2 56 | 57 | inputs = [(tx.blknum1, tx.txindex1, tx.oindex1), (tx.blknum2, tx.txindex2, tx.oindex2)] 58 | for (blknum, txindex, oindex) in inputs: 59 | # Transactions coming from block 0 are valid. 60 | if blknum == 0: 61 | continue 62 | 63 | input_tx = self.blocks[blknum].transaction_set[txindex] 64 | 65 | if oindex == 0: 66 | valid_signature = tx.sig1 != NULL_SIGNATURE and input_tx.newowner1 == tx.sender1 67 | spent = input_tx.spent1 68 | input_amount += input_tx.amount1 69 | else: 70 | valid_signature = tx.sig2 != NULL_SIGNATURE and input_tx.newowner2 == tx.sender2 71 | spent = input_tx.spent2 72 | input_amount += input_tx.amount2 73 | 74 | # Check to see if the input is already spent. 75 | utxo_id = encode_utxo_id(blknum, txindex, oindex) 76 | if spent or utxo_id in temp_spent: 77 | raise TxAlreadySpentException('failed to validate tx') 78 | 79 | if not valid_signature: 80 | raise InvalidTxSignatureException('failed to validate tx') 81 | 82 | if not tx.is_deposit_transaction and input_amount < output_amount: 83 | raise TxAmountMismatchException('failed to validate tx') 84 | 85 | def get_block(self, blknum): 86 | return self.blocks[blknum] 87 | 88 | def get_transaction(self, utxo_id): 89 | (blknum, txindex, _) = decode_utxo_id(utxo_id) 90 | return self.blocks[blknum].transaction_set[txindex] 91 | 92 | def mark_utxo_spent(self, utxo_id): 93 | (_, _, oindex) = decode_utxo_id(utxo_id) 94 | tx = self.get_transaction(utxo_id) 95 | if oindex == 0: 96 | tx.spent1 = True 97 | else: 98 | tx.spent2 = True 99 | 100 | def _apply_transaction(self, tx): 101 | inputs = [(tx.blknum1, tx.txindex1, tx.oindex1), (tx.blknum2, tx.txindex2, tx.oindex2)] 102 | for i in inputs: 103 | (blknum, _, _) = i 104 | if blknum == 0: 105 | continue 106 | input_id = encode_utxo_id(*i) 107 | self.mark_utxo_spent(input_id) 108 | 109 | def _validate_block(self, block): 110 | # Check for a valid signature. 111 | if not block.is_deposit_block and (block.sig == NULL_SIGNATURE or address_to_hex(block.signer) != self.operator.lower()): 112 | raise InvalidBlockSignatureException('failed to validate block') 113 | 114 | for tx in block.transaction_set: 115 | self.validate_transaction(tx) 116 | 117 | def _apply_block(self, block): 118 | for tx in block.transaction_set: 119 | self._apply_transaction(tx) 120 | self.blocks[block.number] = block 121 | -------------------------------------------------------------------------------- /plasma_core/constants.py: -------------------------------------------------------------------------------- 1 | from ethereum import utils as u 2 | 3 | CONTRACT_ADDRESS = '0xA3B2a1804203b75b494028966C0f62e677447A39' 4 | 5 | AUTHORITY = { 6 | 'address': '0xfd02EcEE62797e75D86BCff1642EB0844afB28c7', 7 | 'key': u.normalize_key(b'3bb369fecdc16b93b99514d8ed9c2e87c5824cf4a6a98d2e8e91b7dd0c063304') 8 | } 9 | 10 | ACCOUNTS = [ 11 | { 12 | 'address': '0x4B3eC6c9dC67079E82152d6D55d8dd96a8e6AA26', 13 | 'key': u.normalize_key(b'b937b2c6a606edf1a4d671485f0fa61dcc5102e1ebca392f5a8140b23a8ac04f') 14 | }, 15 | { 16 | 'address': '0xda20A48913f9031337a5e32325F743e8536860e2', 17 | 'key': u.normalize_key(b'999ba3f77899ba4802af5109221c64a9238a6772718f287a8bd3ca3d1b68187f') 18 | }, 19 | { 20 | 'address': '0xF6d8982698dCC46b8E96e34bC2BF3c97302b9923', 21 | 'key': u.normalize_key(b'ef4134d11aa32bcbd314d3cd94b7a5f93bea2b809007d4307a4393cce0285652') 22 | }, 23 | { 24 | 'address': '0x2d75468C0cafA9D41FC5BF3ccA6C292a3cC03d94', 25 | 'key': u.normalize_key(b'25c8620e5bd51caed1d2ff5e79b43dfbef17d0b4eb38d0db8d834da9de5a6120') 26 | }, 27 | { 28 | 'address': '0xF05B4B746AAd830062505AD0cFd3619917484E46', 29 | 'key': u.normalize_key(b'e3d66a68573a85734e80d3de47b82e13374c2a026f219cb766978510a8b8697e') 30 | }, 31 | { 32 | 'address': '0x81A9bfA79598f1536B4918A6556e9855c5E141d5', 33 | 'key': u.normalize_key(b'81e244b79cef097c187d9299a2fc3a680cf1d2637fb7463ca7aa70445a0a0410') 34 | }, 35 | { 36 | 'address': '0xa669513ad878cC0891d8C305CC21903068a9AFe9', 37 | 'key': u.normalize_key(b'ebcaa9c519c2aaa27e7c1656451b9c72167cadf0fd30bc4bcc3bda6d6fcbd507') 38 | }, 39 | { 40 | 'address': '0xC3AaE3a9be258BD485105ef81EB0D5b677EE26fd', 41 | 'key': u.normalize_key(b'b991543d47829ea4f296d182dfa7088303fb3f04dd0c95a5cb7132397e4a008d') 42 | }, 43 | { 44 | 'address': '0xB9DB71c2D02a1B30dfE29C90738b3228Dd9d2ec2', 45 | 'key': u.normalize_key(b'484eb2f0465e7357575f05bf5af5e77cb4b678fb774dd127d9d99e3d31c5f80e') 46 | } 47 | ] 48 | 49 | NULL_BYTE = b'\x00' 50 | NULL_HASH = NULL_BYTE * 32 51 | NULL_SIGNATURE = NULL_BYTE * 65 52 | NULL_ADDRESS = NULL_BYTE * 20 53 | NULL_ADDRESS_HEX = '0x' + NULL_ADDRESS.hex() 54 | -------------------------------------------------------------------------------- /plasma_core/exceptions.py: -------------------------------------------------------------------------------- 1 | class TxAlreadySpentException(Exception): 2 | """the transaction is already spent""" 3 | 4 | 5 | class InvalidTxSignatureException(Exception): 6 | """the signature of a tx is invalid""" 7 | 8 | 9 | class InvalidBlockSignatureException(Exception): 10 | """the signature of a block is invalid""" 11 | 12 | 13 | class TxAmountMismatchException(Exception): 14 | """tx input total amount is not equal to output total amount""" 15 | 16 | 17 | class InvalidBlockMerkleException(Exception): 18 | """merkle tree of a block is invalid""" 19 | -------------------------------------------------------------------------------- /plasma_core/transaction.py: -------------------------------------------------------------------------------- 1 | import rlp 2 | from rlp.sedes import big_endian_int, binary 3 | from ethereum import utils 4 | from plasma_core.utils.signatures import get_signer, sign 5 | from plasma_core.constants import NULL_SIGNATURE 6 | 7 | 8 | class Transaction(rlp.Serializable): 9 | 10 | fields = [ 11 | ('blknum1', big_endian_int), 12 | ('txindex1', big_endian_int), 13 | ('oindex1', big_endian_int), 14 | ('blknum2', big_endian_int), 15 | ('txindex2', big_endian_int), 16 | ('oindex2', big_endian_int), 17 | ('cur12', utils.address), 18 | ('newowner1', utils.address), 19 | ('amount1', big_endian_int), 20 | ('newowner2', utils.address), 21 | ('amount2', big_endian_int), 22 | ('sig1', binary), 23 | ('sig2', binary), 24 | ] 25 | 26 | def __init__(self, 27 | blknum1, txindex1, oindex1, 28 | blknum2, txindex2, oindex2, 29 | cur12, 30 | newowner1, amount1, 31 | newowner2, amount2, 32 | sig1=NULL_SIGNATURE, 33 | sig2=NULL_SIGNATURE): 34 | # Input 1 35 | self.blknum1 = blknum1 36 | self.txindex1 = txindex1 37 | self.oindex1 = oindex1 38 | self.sig1 = sig1 39 | 40 | # Input 2 41 | self.blknum2 = blknum2 42 | self.txindex2 = txindex2 43 | self.oindex2 = oindex2 44 | self.sig2 = sig2 45 | 46 | # Token addresses 47 | self.cur12 = utils.normalize_address(cur12) 48 | 49 | # Outputs 50 | self.newowner1 = utils.normalize_address(newowner1) 51 | self.amount1 = amount1 52 | 53 | self.newowner2 = utils.normalize_address(newowner2) 54 | self.amount2 = amount2 55 | 56 | self.confirmation1 = None 57 | self.confirmation2 = None 58 | 59 | self.spent1 = False 60 | self.spent2 = False 61 | 62 | @property 63 | def hash(self): 64 | return utils.sha3(self.encoded) 65 | 66 | @property 67 | def merkle_hash(self): 68 | return utils.sha3(self.hash + self.sig1 + self.sig2) 69 | 70 | @property 71 | def is_single_utxo(self): 72 | return self.blknum2 == 0 73 | 74 | @property 75 | def is_deposit_transaction(self): 76 | return self.blknum1 == 0 and self.blknum2 == 0 77 | 78 | @property 79 | def sender1(self): 80 | return get_signer(self.hash, self.sig1) 81 | 82 | @property 83 | def sender2(self): 84 | return get_signer(self.hash, self.sig2) 85 | 86 | @property 87 | def encoded(self): 88 | return rlp.encode(self, UnsignedTransaction) 89 | 90 | def sign1(self, key): 91 | self.sig1 = sign(self.hash, key) 92 | 93 | def sign2(self, key): 94 | self.sig2 = sign(self.hash, key) 95 | 96 | 97 | UnsignedTransaction = Transaction.exclude(['sig1', 'sig2']) 98 | -------------------------------------------------------------------------------- /plasma_core/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma_core/utils/__init__.py -------------------------------------------------------------------------------- /plasma_core/utils/address.py: -------------------------------------------------------------------------------- 1 | def address_to_hex(address): 2 | return '0x' + address.hex() 3 | 4 | 5 | def address_to_bytes(address): 6 | return bytes.fromhex(address[2:]) 7 | -------------------------------------------------------------------------------- /plasma_core/utils/merkle/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/plasma_core/utils/merkle/__init__.py -------------------------------------------------------------------------------- /plasma_core/utils/merkle/exceptions.py: -------------------------------------------------------------------------------- 1 | class MemberNotExistException(Exception): 2 | """raise when a leaf is not in the merkle tree""" 3 | -------------------------------------------------------------------------------- /plasma_core/utils/merkle/fixed_merkle.py: -------------------------------------------------------------------------------- 1 | from ethereum.utils import sha3 2 | from plasma_core.constants import NULL_HASH 3 | from .exceptions import MemberNotExistException 4 | from .node import Node 5 | 6 | 7 | class FixedMerkle(object): 8 | 9 | def __init__(self, depth, leaves=[], hashed=False): 10 | if depth < 1: 11 | raise ValueError('depth should be at least 1') 12 | 13 | self.depth = depth 14 | self.leaf_count = 2 ** depth 15 | self.hashed = hashed 16 | 17 | if len(leaves) > self.leaf_count: 18 | raise ValueError('num of leaves exceed max avaiable num with the depth') 19 | 20 | if not hashed: 21 | leaves = [sha3(leaf) for leaf in leaves] 22 | self.leaves = leaves + [NULL_HASH] * (self.leaf_count - len(leaves)) 23 | self.tree = [self.create_nodes(self.leaves)] 24 | self.create_tree(self.tree[0]) 25 | 26 | def create_nodes(self, leaves): 27 | return [Node(leaf) for leaf in leaves] 28 | 29 | def create_tree(self, leaves): 30 | if len(leaves) == 1: 31 | self.root = leaves[0].data 32 | return self.root 33 | next_level = len(leaves) 34 | tree_level = [] 35 | for i in range(0, next_level, 2): 36 | combined = sha3(leaves[i].data + leaves[i + 1].data) 37 | next_node = Node(combined, leaves[i], leaves[i + 1]) 38 | tree_level.append(next_node) 39 | self.tree.append(tree_level) 40 | self.create_tree(tree_level) 41 | 42 | def check_membership(self, leaf, index, proof): 43 | if not self.hashed: 44 | leaf = sha3(leaf) 45 | computed_hash = leaf 46 | for i in range(0, self.depth * 32, 32): 47 | segment = proof[i:i + 32] 48 | if index % 2 == 0: 49 | computed_hash = sha3(computed_hash + segment) 50 | else: 51 | computed_hash = sha3(segment + computed_hash) 52 | index = index // 2 53 | return computed_hash == self.root 54 | 55 | def create_membership_proof(self, leaf): 56 | if not self.hashed: 57 | leaf = sha3(leaf) 58 | if not self.is_member(leaf): 59 | raise MemberNotExistException('leaf is not in the merkle tree') 60 | 61 | index = self.leaves.index(leaf) 62 | proof = b'' 63 | for i in range(0, self.depth, 1): 64 | if index % 2 == 0: 65 | sibling_index = index + 1 66 | else: 67 | sibling_index = index - 1 68 | index = index // 2 69 | proof += self.tree[i][sibling_index].data 70 | return proof 71 | 72 | def is_member(self, leaf): 73 | return leaf in self.leaves 74 | 75 | def not_member(self, leaf): 76 | return leaf not in self.leaves 77 | -------------------------------------------------------------------------------- /plasma_core/utils/merkle/node.py: -------------------------------------------------------------------------------- 1 | class Node(object): 2 | 3 | def __init__(self, data, left=None, right=None): 4 | self.data = data 5 | self.left = left 6 | self.right = right 7 | -------------------------------------------------------------------------------- /plasma_core/utils/signatures.py: -------------------------------------------------------------------------------- 1 | from ethereum import utils as u 2 | 3 | 4 | def sign(hash, key): 5 | vrs = u.ecsign(hash, key) 6 | rsv = vrs[1:] + vrs[:1] 7 | vrs_bytes = [u.encode_int32(i) for i in rsv[:2]] + [u.int_to_bytes(rsv[2])] 8 | return b''.join(vrs_bytes) 9 | 10 | 11 | def get_signer(hash, sig): 12 | v = sig[64] 13 | if v < 27: 14 | v += 27 15 | r = u.bytes_to_int(sig[:32]) 16 | s = u.bytes_to_int(sig[32:64]) 17 | pub = u.ecrecover_to_pub(hash, v, r, s) 18 | return u.sha3(pub)[-20:] 19 | -------------------------------------------------------------------------------- /plasma_core/utils/transactions.py: -------------------------------------------------------------------------------- 1 | from plasma_core.transaction import Transaction 2 | from plasma_core.constants import NULL_ADDRESS 3 | 4 | 5 | BLKNUM_OFFSET = 1000000000 6 | TXINDEX_OFFSET = 10000 7 | 8 | 9 | def decode_utxo_id(utxo_id): 10 | blknum = utxo_id // BLKNUM_OFFSET 11 | txindex = (utxo_id % BLKNUM_OFFSET) // BLKNUM_OFFSET 12 | oindex = utxo_id - blknum * BLKNUM_OFFSET - txindex * TXINDEX_OFFSET 13 | return (blknum, txindex, oindex) 14 | 15 | 16 | def encode_utxo_id(blknum, txindex, oindex): 17 | return (blknum * BLKNUM_OFFSET) + (txindex * TXINDEX_OFFSET) + (oindex * 1) 18 | 19 | 20 | def decode_tx_id(utxo_id): 21 | (blknum, txindex, _) = decode_utxo_id(utxo_id) 22 | return encode_utxo_id(blknum, txindex, 0) 23 | 24 | 25 | def get_deposit_tx(owner, amount): 26 | return Transaction(0, 0, 0, 27 | 0, 0, 0, 28 | NULL_ADDRESS, 29 | owner, amount, 30 | NULL_ADDRESS, 0) 31 | -------------------------------------------------------------------------------- /plasma_core/utils/utils.py: -------------------------------------------------------------------------------- 1 | from ethereum import utils as u 2 | from plasma_core.constants import NULL_HASH 3 | from plasma_core.utils.signatures import sign 4 | from plasma_core.utils.merkle.fixed_merkle import FixedMerkle 5 | 6 | 7 | def get_empty_merkle_tree_hash(depth): 8 | zeroes_hash = NULL_HASH 9 | for _ in range(depth): 10 | zeroes_hash = u.sha3(zeroes_hash + zeroes_hash) 11 | return zeroes_hash 12 | 13 | 14 | def get_merkle_of_leaves(depth, leaves): 15 | return FixedMerkle(depth, leaves) 16 | 17 | 18 | def bytes_fill_left(inp, length): 19 | return bytes(length - len(inp)) + inp 20 | 21 | 22 | def get_deposit_hash(owner, token, value): 23 | return u.sha3(owner + token + b'\x00' * 31 + u.int_to_bytes(value)) 24 | 25 | 26 | def confirm_tx(tx, root, key): 27 | return sign(u.sha3(tx.hash + root), key) 28 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | E501 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | with open('README.md') as f: 7 | readme = f.read() 8 | 9 | setup( 10 | name='plasma', 11 | description='Plasma MVP', 12 | long_description=readme, 13 | author='David Knott', 14 | author_email='', 15 | license=license, 16 | packages=find_packages(exclude=('tests')), 17 | include_package_data=True, 18 | install_requires=[ 19 | 'ethereum==2.3.0', 20 | 'web3==4.5.0', 21 | 'werkzeug==0.14.1', 22 | 'json-rpc==1.10.8', 23 | 'py-solc', 24 | 'click==6.7', 25 | 'pytest', 26 | 'flake8==3.5.0', 27 | 'rlp==0.6.0' 28 | ], 29 | entry_points={ 30 | 'console_scripts': ["omg=plasma.cli:cli"], 31 | } 32 | ) 33 | -------------------------------------------------------------------------------- /testlang/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-mvp/82e26b5efadb57c00683f18f11758f59c7a98876/testlang/__init__.py -------------------------------------------------------------------------------- /testlang/testing_language.py: -------------------------------------------------------------------------------- 1 | import time 2 | from plasma.root_chain.deployer import Deployer 3 | from plasma.child_chain.child_chain import ChildChain 4 | from plasma_core.transaction import Transaction 5 | from plasma_core.utils.utils import confirm_tx 6 | from plasma_core.utils.transactions import encode_utxo_id, decode_utxo_id 7 | from plasma_core.constants import AUTHORITY, ACCOUNTS, NULL_ADDRESS, NULL_ADDRESS_HEX 8 | 9 | 10 | class TestingLanguage(object): 11 | 12 | def __init__(self): 13 | self.root_chain = Deployer().deploy_contract('RootChain', concise=False) 14 | self.child_chain = ChildChain(AUTHORITY['address'], self.root_chain) 15 | self.confirmations = {} 16 | self.accounts = [] 17 | 18 | def get_account(self): 19 | account = ACCOUNTS[len(self.accounts)] 20 | self.accounts.append(account) 21 | return account 22 | 23 | def deposit(self, account, amount): 24 | deposit_blknum = self.child_chain.chain.next_deposit_block 25 | 26 | self.root_chain.transact({ 27 | 'from': account['address'], 28 | 'value': amount 29 | }).deposit() 30 | 31 | # Wait for the Deposit event to be detected 32 | time.sleep(1) 33 | 34 | return encode_utxo_id(deposit_blknum, 0, 0) 35 | 36 | def transfer(self, 37 | input1, newowner1, amount1, signatory1, 38 | input2=0, newowner2=None, amount2=0, signatory2=None, cur12=NULL_ADDRESS): 39 | newowner_address1 = newowner1['address'] 40 | newowner_address2 = NULL_ADDRESS 41 | if newowner2 is not None: 42 | newowner_address2 = newowner2['address'] 43 | 44 | tx = Transaction(*decode_utxo_id(input1), 45 | *decode_utxo_id(input2), 46 | cur12, 47 | newowner_address1, amount1, 48 | newowner_address2, amount2) 49 | 50 | if signatory1 is not None: 51 | key1 = signatory1['key'] 52 | tx.sign1(key1) 53 | 54 | if signatory2 is not None: 55 | key2 = signatory2['key'] 56 | tx.sign2(key2) 57 | 58 | spend_id = self.child_chain.apply_transaction(tx) 59 | self.submit_block() 60 | return spend_id 61 | 62 | def submit_block(self, signatory=AUTHORITY): 63 | signing_key = None 64 | if signatory is not None: 65 | signing_key = signatory['key'] 66 | 67 | block = self.child_chain.current_block 68 | if signing_key: 69 | block.sign(signing_key) 70 | 71 | self.child_chain.submit_block(block) 72 | 73 | def confirm(self, tx_id, signatory1, signatory2=None): 74 | tx = self.child_chain.get_transaction(tx_id) 75 | (blknum, _, _) = decode_utxo_id(tx_id) 76 | block_root = self.child_chain.get_block(blknum).root 77 | 78 | confirm_sigs = b'' 79 | for signatory in [x for x in [signatory1, signatory2] if x is not None]: 80 | confirm_sigs += confirm_tx(tx, block_root, signatory['key']) 81 | 82 | self.confirmations[tx_id] = confirm_sigs 83 | 84 | def start_deposit_exit(self, utxo_id, exitor): 85 | tx = self.child_chain.get_transaction(utxo_id) 86 | 87 | exit_bond = self.root_chain.functions.EXIT_BOND().call() 88 | self.root_chain.transact({ 89 | 'from': exitor['address'], 90 | 'value': exit_bond 91 | }).startDepositExit(utxo_id, NULL_ADDRESS_HEX, tx.amount1) 92 | 93 | def start_exit(self, utxo_id, exitor): 94 | tx = self.child_chain.get_transaction(utxo_id) 95 | 96 | sigs = tx.sig1 + tx.sig2 + self.confirmations[utxo_id] 97 | (blknum, _, _) = decode_utxo_id(utxo_id) 98 | block = self.child_chain.get_block(blknum) 99 | proof = block.merkle.create_membership_proof(tx.merkle_hash) 100 | exit_bond = self.root_chain.functions.EXIT_BOND().call() 101 | 102 | self.root_chain.transact({ 103 | 'from': exitor['address'], 104 | 'value': exit_bond 105 | }).startExit(utxo_id, tx.encoded, proof, sigs) 106 | -------------------------------------------------------------------------------- /tests/child_chain/test_block.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from plasma_core.block import Block 3 | from plasma_core.constants import NULL_SIGNATURE 4 | from plasma_core.utils.signatures import sign, get_signer 5 | 6 | 7 | @pytest.fixture 8 | def block(): 9 | return Block() 10 | 11 | 12 | def test_initial_state(block): 13 | assert block.transaction_set == [] 14 | assert block.sig == NULL_SIGNATURE 15 | 16 | 17 | def test_signature(t, block): 18 | block.sign(t.k0) 19 | assert block.sig == sign(block.hash, t.k0) 20 | assert block.signer == get_signer(block.hash, sign(block.hash, t.k0)) 21 | -------------------------------------------------------------------------------- /tests/child_chain/test_child_chain.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from plasma_core.exceptions import (InvalidBlockSignatureException, 3 | InvalidTxSignatureException, 4 | TxAlreadySpentException) 5 | 6 | 7 | def test_apply_deposit(test_lang): 8 | owner = test_lang.get_account() 9 | amount = 100 10 | 11 | test_lang.deposit(owner, amount) 12 | 13 | deposit_block_number = 1 14 | deposit_block = test_lang.child_chain.get_block(deposit_block_number) 15 | assert len(deposit_block.transaction_set) == 1 16 | 17 | 18 | def test_send_tx_with_sig(test_lang): 19 | owner_1 = test_lang.get_account() 20 | owner_2 = test_lang.get_account() 21 | amount = 100 22 | 23 | deposit_id = test_lang.deposit(owner_1, amount) 24 | test_lang.transfer(deposit_id, owner_2, amount, owner_1) 25 | 26 | 27 | def test_send_tx_no_sig(test_lang): 28 | owner_1 = test_lang.get_account() 29 | owner_2 = test_lang.get_account() 30 | amount = 100 31 | key = None 32 | 33 | deposit_id = test_lang.deposit(owner_1, amount) 34 | 35 | with pytest.raises(InvalidTxSignatureException): 36 | test_lang.transfer(deposit_id, owner_2, amount, key) 37 | 38 | 39 | def test_send_tx_invalid_sig(test_lang): 40 | owner_1 = test_lang.get_account() 41 | owner_2 = test_lang.get_account() 42 | owner_3 = test_lang.get_account() 43 | amount = 100 44 | 45 | deposit_id = test_lang.deposit(owner_1, amount) 46 | 47 | with pytest.raises(InvalidTxSignatureException): 48 | test_lang.transfer(deposit_id, owner_2, amount, owner_3) 49 | 50 | 51 | def test_send_tx_double_spend(test_lang): 52 | owner_1 = test_lang.get_account() 53 | owner_2 = test_lang.get_account() 54 | amount = 100 55 | 56 | deposit_id = test_lang.deposit(owner_1, amount) 57 | test_lang.transfer(deposit_id, owner_2, amount, owner_1) 58 | 59 | with pytest.raises(TxAlreadySpentException): 60 | test_lang.transfer(deposit_id, owner_2, amount, owner_1) 61 | 62 | 63 | def test_submit_block(test_lang): 64 | old_block_number = test_lang.child_chain.get_current_block().number 65 | test_lang.submit_block() 66 | new_block_number = test_lang.child_chain.get_current_block().number 67 | assert new_block_number == old_block_number + test_lang.child_chain.chain.child_block_interval 68 | 69 | 70 | def test_submit_block_no_sig(test_lang): 71 | with pytest.raises(InvalidBlockSignatureException): 72 | test_lang.submit_block(None) 73 | 74 | 75 | def test_submit_block_invalid_sig(test_lang): 76 | owner_1 = test_lang.get_account() 77 | 78 | with pytest.raises(InvalidBlockSignatureException): 79 | test_lang.submit_block(owner_1) 80 | -------------------------------------------------------------------------------- /tests/child_chain/test_child_chain_integration.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | from plasma_core.constants import NULL_ADDRESS, NULL_ADDRESS_HEX 3 | from plasma_core.transaction import Transaction 4 | from plasma_core.utils.transactions import decode_utxo_id 5 | 6 | 7 | def test_deposit(test_lang): 8 | owner_1 = test_lang.get_account() 9 | amount = 100 10 | 11 | deposit_id = test_lang.deposit(owner_1, amount) 12 | 13 | tx = Transaction(0, 0, 0, 0, 0, 0, NULL_ADDRESS, owner_1['address'], amount, NULL_ADDRESS, 0) 14 | deposit_hash = Web3.soliditySha3(['address', 'address', 'uint256'], [owner_1['address'], NULL_ADDRESS_HEX, amount]) # pylint: disable=E1120 15 | 16 | (deposit_blknum, _, _) = decode_utxo_id(deposit_id) 17 | deposit_block = test_lang.child_chain.get_block(deposit_blknum) 18 | assert deposit_block.transaction_set[0].hash == tx.hash 19 | assert test_lang.root_chain.call().getPlasmaBlock(deposit_blknum)[0] == deposit_hash 20 | 21 | 22 | def test_transfer(test_lang): 23 | owner_1 = test_lang.get_account() 24 | owner_2 = test_lang.get_account() 25 | amount = 100 26 | 27 | deposit_id = test_lang.deposit(owner_1, amount) 28 | transfer_id = test_lang.transfer(deposit_id, owner_2, amount, owner_1) 29 | 30 | tx = Transaction(1, 0, 0, 31 | 0, 0, 0, 32 | NULL_ADDRESS, 33 | owner_2['address'], amount, 34 | NULL_ADDRESS, 0) 35 | 36 | assert test_lang.child_chain.get_transaction(transfer_id).hash == tx.hash 37 | 38 | 39 | def test_submit_block(test_lang): 40 | owner_1 = test_lang.get_account() 41 | owner_2 = test_lang.get_account() 42 | amount = 100 43 | 44 | deposit_id = test_lang.deposit(owner_1, amount) 45 | test_lang.transfer(deposit_id, owner_2, amount, owner_1) 46 | 47 | blknum = 1000 48 | assert test_lang.root_chain.call().getPlasmaBlock(blknum)[0] == test_lang.child_chain.get_block(blknum).root 49 | 50 | 51 | def test_withdraw_transfer(test_lang): 52 | owner_1 = test_lang.get_account() 53 | owner_2 = test_lang.get_account() 54 | amount = 100 55 | 56 | deposit_id = test_lang.deposit(owner_1, amount) 57 | transfer_id = test_lang.transfer(deposit_id, owner_2, amount, owner_1) 58 | test_lang.confirm(transfer_id, owner_1) 59 | test_lang.start_exit(transfer_id, owner_2) 60 | 61 | exit_data = test_lang.root_chain.call().getExit(1000000000000) 62 | assert exit_data[0] == owner_2['address'] 63 | assert exit_data[1] == NULL_ADDRESS_HEX 64 | assert exit_data[2] == amount 65 | 66 | 67 | def test_withdraw_deposit(test_lang): 68 | owner_1 = test_lang.get_account() 69 | amount = 100 70 | 71 | deposit_id = test_lang.deposit(owner_1, amount) 72 | test_lang.start_deposit_exit(deposit_id, owner_1) 73 | 74 | exit_data = test_lang.root_chain.call().getExit(1000000000) 75 | assert exit_data[0] == owner_1['address'] 76 | assert exit_data[1] == NULL_ADDRESS_HEX 77 | assert exit_data[2] == amount 78 | -------------------------------------------------------------------------------- /tests/child_chain/test_transaction.py: -------------------------------------------------------------------------------- 1 | from plasma_core.transaction import Transaction 2 | from plasma_core.constants import NULL_ADDRESS, NULL_SIGNATURE 3 | 4 | 5 | def test_transaction(t): 6 | blknum1, txindex1, oindex1 = 1, 1, 0 7 | blknum2, txindex2, oindex2 = 2, 2, 1 8 | newowner1, amount1 = t.a1, 100 9 | newowner2, amount2 = t.a2, 150 10 | oldowner1, oldowner2 = t.a1, t.a2 11 | key1, key2 = t.k1, t.k2 12 | tx = Transaction(blknum1, txindex1, oindex1, 13 | blknum2, txindex2, oindex2, 14 | NULL_ADDRESS, 15 | newowner1, amount1, 16 | newowner2, amount2) 17 | assert tx.blknum1 == blknum1 18 | assert tx.txindex1 == txindex1 19 | assert tx.oindex1 == oindex1 20 | assert tx.blknum2 == blknum2 21 | assert txindex2 == txindex2 22 | assert tx.oindex2 == oindex2 23 | assert tx.newowner1 == newowner1 24 | assert tx.amount1 == amount1 25 | assert tx.newowner2 == newowner2 26 | assert tx.amount2 == amount2 27 | assert tx.sig1 == NULL_SIGNATURE 28 | assert tx.sig2 == NULL_SIGNATURE 29 | tx.sign1(key1) 30 | assert tx.sender1 == oldowner1 31 | tx.sign2(key2) 32 | assert tx.sender2 == oldowner2 33 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from ethereum.tools import tester, _solidity 4 | from ethereum.abi import ContractTranslator 5 | from ethereum import utils 6 | from plasma_core.utils import utils as plasma_utils 7 | from plasma.root_chain.deployer import Deployer 8 | from testlang.testing_language import TestingLanguage 9 | 10 | 11 | OWN_DIR = os.path.dirname(os.path.realpath(__file__)) 12 | 13 | # Compile contracts once before tests start 14 | deployer = Deployer() 15 | deployer.compile_all() 16 | 17 | 18 | @pytest.fixture 19 | def t(): 20 | tester.chain = tester.Chain() 21 | return tester 22 | 23 | 24 | def get_dirs(path): 25 | abs_contract_path = os.path.realpath(os.path.join(OWN_DIR, '..', 'plasma', 'root_chain', 'contracts')) 26 | sub_dirs = [x[0] for x in os.walk(abs_contract_path)] 27 | extra_args = ' '.join(['{}={}'.format(d.split('/')[-1], d) for d in sub_dirs]) 28 | path = '{}/{}'.format(abs_contract_path, path) 29 | return path, extra_args 30 | 31 | 32 | def create_abi(path): 33 | path, extra_args = get_dirs(path) 34 | abi = _solidity.compile_last_contract(path, combined='abi', extra_args=extra_args)['abi'] 35 | return ContractTranslator(abi) 36 | 37 | 38 | @pytest.fixture 39 | def assert_tx_failed(t): 40 | def assert_tx_failed(function_to_test, exception=tester.TransactionFailed): 41 | initial_state = t.chain.snapshot() 42 | with pytest.raises(exception): 43 | function_to_test() 44 | t.chain.revert(initial_state) 45 | return assert_tx_failed 46 | 47 | 48 | @pytest.fixture 49 | def u(): 50 | utils.plasma = plasma_utils 51 | return utils 52 | 53 | 54 | @pytest.fixture 55 | def get_contract(t, u): 56 | def create_contract(path, args=(), sender=t.k0): 57 | abi, hexcode = deployer.get_contract_data(path) 58 | bytecode = u.decode_hex(hexcode) 59 | ct = ContractTranslator(abi) 60 | code = bytecode + (ct.encode_constructor_arguments(args) if args else b'') 61 | address = t.chain.tx(sender=sender, to=b'', startgas=(4 * 10 ** 6 + 5 * 10 ** 5), value=0, data=code) 62 | return t.ABIContract(t.chain, abi, address) 63 | return create_contract 64 | 65 | 66 | @pytest.fixture 67 | def bytes_helper(): 68 | def bytes_helper(inp, length): 69 | return bytes(length - len(inp)) + inp 70 | return bytes_helper 71 | 72 | 73 | @pytest.fixture() 74 | def test_lang(): 75 | t = TestingLanguage() 76 | yield t 77 | t.child_chain.event_listener.stop_all() 78 | -------------------------------------------------------------------------------- /tests/root_chain/contracts/data_structures/test_priority_queue.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ethereum.tools.tester import TransactionFailed 3 | 4 | 5 | @pytest.fixture 6 | def priority_queue(get_contract): 7 | return get_contract('PriorityQueue') 8 | 9 | 10 | def test_priority_queue_get_min_empty_should_fail(priority_queue): 11 | with pytest.raises(TransactionFailed): 12 | priority_queue.getMin() 13 | 14 | 15 | def test_priority_queue_insert(priority_queue): 16 | extra_value = 12345 17 | priority_queue.insert(2, extra_value) 18 | assert priority_queue.getMin() == [2, extra_value] 19 | assert priority_queue.currentSize() == 1 20 | 21 | 22 | def test_priority_queue_insert_multiple(priority_queue): 23 | extra_value = 12345 24 | priority_queue.insert(2, extra_value) 25 | priority_queue.insert(5, extra_value) 26 | assert priority_queue.getMin() == [2, extra_value] 27 | assert priority_queue.currentSize() == 2 28 | 29 | 30 | def test_priority_queue_insert_out_of_order(priority_queue): 31 | extra_value = 12345 32 | priority_queue.insert(5, extra_value) 33 | priority_queue.insert(2, extra_value) 34 | assert priority_queue.getMin() == [2, extra_value] 35 | 36 | 37 | def test_priority_queue_delete_min(priority_queue): 38 | extra_value = 12345 39 | priority_queue.insert(2, extra_value) 40 | assert priority_queue.delMin() == [2, extra_value] 41 | assert priority_queue.currentSize() == 0 42 | 43 | 44 | def test_priority_queue_delete_all(priority_queue): 45 | extra_value = 12345 46 | priority_queue.insert(5, extra_value) 47 | priority_queue.insert(2, extra_value) 48 | assert priority_queue.delMin() == [2, extra_value] 49 | assert priority_queue.delMin() == [5, extra_value] 50 | assert priority_queue.currentSize() == 0 51 | with pytest.raises(TransactionFailed): 52 | priority_queue.getMin() 53 | 54 | 55 | def test_priority_queue_delete_then_insert(priority_queue): 56 | extra_value = 12345 57 | priority_queue.insert(2, extra_value) 58 | assert priority_queue.delMin() == [2, extra_value] 59 | priority_queue.insert(5, extra_value) 60 | assert priority_queue.getMin() == [5, extra_value] 61 | -------------------------------------------------------------------------------- /tests/root_chain/contracts/root_chain/test_root_chain.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import rlp 3 | from plasma_core.transaction import Transaction, UnsignedTransaction 4 | from plasma_core.utils.merkle.fixed_merkle import FixedMerkle 5 | from plasma_core.utils.utils import confirm_tx, get_deposit_hash 6 | from plasma_core.utils.transactions import encode_utxo_id, decode_utxo_id 7 | from plasma_core.constants import NULL_ADDRESS, NULL_ADDRESS_HEX 8 | 9 | 10 | @pytest.fixture 11 | def root_chain(t, get_contract): 12 | contract = get_contract('RootChain') 13 | t.chain.mine() 14 | return contract 15 | 16 | 17 | def test_deposit(t, u, root_chain): 18 | owner, value_1 = t.a0, 100 19 | blknum = root_chain.getDepositBlock() 20 | root_chain.deposit(value=value_1) 21 | assert root_chain.getPlasmaBlock(blknum)[0] == u.sha3(owner + b'\x00' * 31 + NULL_ADDRESS + u.int_to_bytes(value_1)) 22 | assert root_chain.getPlasmaBlock(blknum)[1] == t.chain.head_state.timestamp 23 | assert root_chain.getDepositBlock() == blknum + 1 24 | 25 | 26 | def test_start_deposit_exit(t, u, root_chain, assert_tx_failed): 27 | two_weeks = 60 * 60 * 24 * 7 * 2 28 | value_1 = 100 29 | # Deposit once to make sure everything works for deposit block 30 | root_chain.deposit(value=value_1) 31 | blknum = root_chain.getDepositBlock() 32 | root_chain.deposit(value=value_1) 33 | expected_utxo_pos = encode_utxo_id(blknum, 0, 0) 34 | expected_exitable_at = t.chain.head_state.timestamp + two_weeks 35 | exit_bond = root_chain.EXIT_BOND() 36 | root_chain.startDepositExit(expected_utxo_pos, NULL_ADDRESS, value_1, value=exit_bond) 37 | exitable_at, utxo_pos = root_chain.getNextExit(NULL_ADDRESS) 38 | assert utxo_pos == expected_utxo_pos 39 | assert exitable_at == expected_exitable_at 40 | assert root_chain.exits(utxo_pos) == ['0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1', NULL_ADDRESS_HEX, 100] 41 | # Same deposit cannot be exited twice 42 | assert_tx_failed(lambda: root_chain.startDepositExit(utxo_pos, NULL_ADDRESS, value_1, value=exit_bond)) 43 | # Fails if transaction sender is not the depositor 44 | assert_tx_failed(lambda: root_chain.startDepositExit(utxo_pos, NULL_ADDRESS, value_1, sender=t.k1, value=exit_bond)) 45 | # Fails if utxo_pos is wrong 46 | assert_tx_failed(lambda: root_chain.startDepositExit(utxo_pos * 2, NULL_ADDRESS, value_1, value=exit_bond)) 47 | # Fails if value given is not equal to deposited value 48 | assert_tx_failed(lambda: root_chain.startDepositExit(utxo_pos, NULL_ADDRESS, value_1 + 1, value=exit_bond)) 49 | 50 | 51 | def test_start_fee_exit(t, u, root_chain, assert_tx_failed): 52 | two_weeks = 60 * 60 * 24 * 7 * 2 53 | value_1 = 100 54 | blknum = root_chain.getDepositBlock() 55 | root_chain.deposit(value=value_1) 56 | expected_utxo_pos = root_chain.currentFeeExit() 57 | expected_exitable_at = t.chain.head_state.timestamp + two_weeks + 1 58 | assert root_chain.currentFeeExit() == 1 59 | exit_bond = root_chain.EXIT_BOND() 60 | root_chain.startFeeExit(NULL_ADDRESS, 1, value=exit_bond) 61 | assert root_chain.currentFeeExit() == 2 62 | exitable_at, utxo_pos = root_chain.getNextExit(NULL_ADDRESS) 63 | fee_priority = exitable_at << 128 | utxo_pos 64 | assert utxo_pos == expected_utxo_pos 65 | assert exitable_at == expected_exitable_at 66 | 67 | expected_utxo_pos = encode_utxo_id(blknum, 0, 0) 68 | root_chain.startDepositExit(expected_utxo_pos, NULL_ADDRESS, value_1, value=exit_bond) 69 | created_at, utxo_pos = root_chain.getNextExit(NULL_ADDRESS) 70 | deposit_priority = created_at << 128 | utxo_pos 71 | assert fee_priority > deposit_priority 72 | # Fails if transaction sender isn't the authority 73 | assert_tx_failed(lambda: root_chain.startFeeExit(NULL_ADDRESS, 1, sender=t.k1, value=exit_bond)) 74 | 75 | 76 | def test_start_exit(t, root_chain, assert_tx_failed): 77 | week_and_a_half = 60 * 60 * 24 * 13 78 | owner, value_1, key = t.a1, 100, t.k1 79 | tx1 = Transaction(0, 0, 0, 0, 0, 0, 80 | NULL_ADDRESS, 81 | owner, value_1, NULL_ADDRESS, 0) 82 | deposit_tx_hash = get_deposit_hash(owner, NULL_ADDRESS, value_1) 83 | dep_blknum = root_chain.getDepositBlock() 84 | assert dep_blknum == 1 85 | root_chain.deposit(value=value_1, sender=key) 86 | merkle = FixedMerkle(16, [deposit_tx_hash], True) 87 | proof = merkle.create_membership_proof(deposit_tx_hash) 88 | confirmSig1 = confirm_tx(tx1, root_chain.getPlasmaBlock(dep_blknum)[0], key) 89 | snapshot = t.chain.snapshot() 90 | sigs = tx1.sig1 + tx1.sig2 + confirmSig1 91 | utxoId = encode_utxo_id(dep_blknum, 0, 0) 92 | # Deposit exit 93 | exit_bond = root_chain.EXIT_BOND() 94 | root_chain.startDepositExit(utxoId, NULL_ADDRESS, tx1.amount1, sender=key, value=exit_bond) 95 | 96 | t.chain.head_state.timestamp += week_and_a_half 97 | # Cannot exit twice off of the same utxo 98 | utxo_pos1 = encode_utxo_id(dep_blknum, 0, 0) 99 | assert_tx_failed(lambda: root_chain.startExit(utxo_pos1, deposit_tx_hash, proof, sigs, sender=key, value=exit_bond)) 100 | assert root_chain.getExit(utxo_pos1) == ['0x' + owner.hex(), NULL_ADDRESS_HEX, 100] 101 | t.chain.revert(snapshot) 102 | 103 | tx2 = Transaction(dep_blknum, 0, 0, 0, 0, 0, 104 | NULL_ADDRESS, 105 | owner, value_1, NULL_ADDRESS, 0) 106 | tx2.sign1(key) 107 | tx_bytes2 = rlp.encode(tx2, UnsignedTransaction) 108 | merkle = FixedMerkle(16, [tx2.merkle_hash], True) 109 | proof = merkle.create_membership_proof(tx2.merkle_hash) 110 | child_blknum = root_chain.currentChildBlock() 111 | assert child_blknum == 1000 112 | root_chain.submitBlock(merkle.root) 113 | confirmSig1 = confirm_tx(tx2, root_chain.getPlasmaBlock(child_blknum)[0], key) 114 | sigs = tx2.sig1 + tx2.sig2 + confirmSig1 115 | snapshot = t.chain.snapshot() 116 | # # Single input exit 117 | utxo_pos2 = encode_utxo_id(child_blknum, 0, 0) 118 | root_chain.startExit(utxo_pos2, tx_bytes2, proof, sigs, sender=key, value=exit_bond) 119 | assert root_chain.getExit(utxo_pos2) == ['0x' + owner.hex(), NULL_ADDRESS_HEX, 100] 120 | t.chain.revert(snapshot) 121 | dep2_blknum = root_chain.getDepositBlock() 122 | assert dep2_blknum == 1001 123 | root_chain.deposit(value=value_1, sender=key) 124 | tx3 = Transaction(child_blknum, 0, 0, dep2_blknum, 0, 0, 125 | NULL_ADDRESS, 126 | owner, value_1, NULL_ADDRESS, 0, 0) 127 | tx3.sign1(key) 128 | tx3.sign2(key) 129 | tx_bytes3 = rlp.encode(tx3, UnsignedTransaction) 130 | merkle = FixedMerkle(16, [tx3.merkle_hash], True) 131 | proof = merkle.create_membership_proof(tx3.merkle_hash) 132 | child2_blknum = root_chain.currentChildBlock() 133 | assert child2_blknum == 2000 134 | root_chain.submitBlock(merkle.root) 135 | confirmSig1 = confirm_tx(tx3, root_chain.getPlasmaBlock(child2_blknum)[0], key) 136 | confirmSig2 = confirm_tx(tx3, root_chain.getPlasmaBlock(child2_blknum)[0], key) 137 | sigs = tx3.sig1 + tx3.sig2 + confirmSig1 + confirmSig2 138 | # Double input exit 139 | utxo_pos3 = encode_utxo_id(child2_blknum, 0, 0) 140 | root_chain.startExit(utxo_pos3, tx_bytes3, proof, sigs, sender=key, value=exit_bond) 141 | assert root_chain.getExit(utxo_pos3) == ['0x' + owner.hex(), NULL_ADDRESS_HEX, 100] 142 | 143 | 144 | def test_challenge_exit(t, u, root_chain, assert_tx_failed): 145 | owner, value_1, key = t.a1, 100, t.k1 146 | tx1 = Transaction(0, 0, 0, 0, 0, 0, 147 | NULL_ADDRESS, 148 | owner, value_1, NULL_ADDRESS, 0) 149 | deposit_tx_hash = get_deposit_hash(owner, NULL_ADDRESS, value_1) 150 | utxo_pos1 = encode_utxo_id(root_chain.getDepositBlock(), 0, 0) 151 | root_chain.deposit(value=value_1, sender=key) 152 | utxo_pos2 = encode_utxo_id(root_chain.getDepositBlock(), 0, 0) 153 | root_chain.deposit(value=value_1, sender=key) 154 | merkle = FixedMerkle(16, [deposit_tx_hash], True) 155 | proof = merkle.create_membership_proof(deposit_tx_hash) 156 | confirmSig1 = confirm_tx(tx1, root_chain.getPlasmaBlock(utxo_pos1)[0], key) 157 | sigs = tx1.sig1 + tx1.sig2 + confirmSig1 158 | exit_bond = root_chain.EXIT_BOND() 159 | root_chain.startDepositExit(utxo_pos1, NULL_ADDRESS, tx1.amount1, sender=key, value=exit_bond) 160 | tx3 = Transaction(utxo_pos2, 0, 0, 0, 0, 0, 161 | NULL_ADDRESS, 162 | owner, value_1, NULL_ADDRESS, 0) 163 | tx3.sign1(key) 164 | tx_bytes3 = rlp.encode(tx3, UnsignedTransaction) 165 | merkle = FixedMerkle(16, [tx3.merkle_hash], True) 166 | proof = merkle.create_membership_proof(tx3.merkle_hash) 167 | child_blknum = root_chain.currentChildBlock() 168 | root_chain.submitBlock(merkle.root) 169 | confirmSig = confirm_tx(tx3, root_chain.getPlasmaBlock(child_blknum)[0], key) 170 | sigs = tx3.sig1 + tx3.sig2 171 | utxo_pos3 = encode_utxo_id(child_blknum, 0, 0) 172 | 173 | utxo1_blknum, _, _ = decode_utxo_id(utxo_pos1) 174 | tx4 = Transaction(utxo1_blknum, 0, 0, 0, 0, 0, 175 | NULL_ADDRESS, 176 | owner, value_1, NULL_ADDRESS, 0) 177 | tx4.sign1(key) 178 | tx_bytes4 = rlp.encode(tx4, UnsignedTransaction) 179 | merkle = FixedMerkle(16, [tx4.merkle_hash], True) 180 | proof = merkle.create_membership_proof(tx4.merkle_hash) 181 | child_blknum = root_chain.currentChildBlock() 182 | root_chain.submitBlock(merkle.root) 183 | confirmSig = confirm_tx(tx4, root_chain.getPlasmaBlock(child_blknum)[0], key) 184 | sigs = tx4.sig1 + tx4.sig2 185 | utxo_pos4 = encode_utxo_id(child_blknum, 0, 0) 186 | oindex1 = 0 187 | assert root_chain.exits(utxo_pos1) == ['0x' + owner.hex(), NULL_ADDRESS_HEX, 100] 188 | # Fails if transaction after exit doesn't reference the utxo being exited 189 | assert_tx_failed(lambda: root_chain.challengeExit(utxo_pos3, oindex1, tx_bytes3, proof, sigs, confirmSig)) 190 | # Fails if transaction proof is incorrect 191 | assert_tx_failed(lambda: root_chain.challengeExit(utxo_pos4, oindex1, tx_bytes4, proof[::-1], sigs, confirmSig)) 192 | # Fails if transaction confirmation is incorrect 193 | assert_tx_failed(lambda: root_chain.challengeExit(utxo_pos4, oindex1, tx_bytes4, proof, sigs, confirmSig[::-1])) 194 | root_chain.challengeExit(utxo_pos4, oindex1, tx_bytes4, proof, sigs, confirmSig) 195 | assert root_chain.exits(utxo_pos1) == [NULL_ADDRESS_HEX, NULL_ADDRESS_HEX, value_1] 196 | 197 | 198 | def test_finalize_exits(t, u, root_chain): 199 | two_weeks = 60 * 60 * 24 * 14 200 | owner, value_1, key = t.a1, 100, t.k1 201 | tx1 = Transaction(0, 0, 0, 0, 0, 0, 202 | NULL_ADDRESS, 203 | owner, value_1, NULL_ADDRESS, 0) 204 | dep1_blknum = root_chain.getDepositBlock() 205 | root_chain.deposit(value=value_1, sender=key) 206 | utxo_pos1 = encode_utxo_id(dep1_blknum, 0, 0) 207 | exit_bond = root_chain.EXIT_BOND() 208 | root_chain.startDepositExit(utxo_pos1, NULL_ADDRESS, tx1.amount1, sender=key, value=exit_bond) 209 | t.chain.head_state.timestamp += two_weeks * 2 210 | assert root_chain.exits(utxo_pos1) == ['0x' + owner.hex(), NULL_ADDRESS_HEX, 100] 211 | pre_balance = t.chain.head_state.get_balance(owner) 212 | root_chain.finalizeExits(NULL_ADDRESS, sender=t.k2) 213 | post_balance = t.chain.head_state.get_balance(owner) 214 | assert post_balance == pre_balance + value_1 + exit_bond 215 | assert root_chain.exits(utxo_pos1) == [NULL_ADDRESS_HEX, NULL_ADDRESS_HEX, value_1] 216 | -------------------------------------------------------------------------------- /tests/utils/test_fixed_merkle.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ethereum.utils import sha3 3 | from plasma_core.constants import NULL_HASH 4 | from plasma_core.utils.merkle.fixed_merkle import FixedMerkle 5 | from plasma_core.utils.utils import get_empty_merkle_tree_hash 6 | 7 | 8 | def test_initial_state(): 9 | assert FixedMerkle(2).leaves == [NULL_HASH] * 4 10 | assert FixedMerkle(3).leaves == [NULL_HASH] * 8 11 | assert FixedMerkle(12).leaves == [NULL_HASH] * (2**12) 12 | 13 | 14 | def test_initialize_with_leaves(): 15 | leaves_1 = [b'a', b'c', b'c'] 16 | leaves_2 = [b'a', b'c', b'c', b'd', b'e'] 17 | assert FixedMerkle(2, leaves_1, True).leaves == leaves_1 + [NULL_HASH] 18 | assert FixedMerkle(3, leaves_2, True).leaves == leaves_2 + [NULL_HASH] * 3 19 | 20 | 21 | def test_initialize_with_leaves_more_than_depth_permits(): 22 | depth = 1 23 | leaves = [b'dummy leaf'] * (2**depth + 1) 24 | with pytest.raises(ValueError) as e: 25 | FixedMerkle(depth, leaves, True) 26 | assert str(e.value) == 'num of leaves exceed max avaiable num with the depth' 27 | 28 | 29 | def test_hash_empty_tree(): 30 | root_1 = sha3(NULL_HASH + NULL_HASH) 31 | root_2 = sha3(root_1 + root_1) 32 | assert FixedMerkle(1, [], True).root == root_1 33 | assert FixedMerkle(2, [], True).root == root_2 34 | assert FixedMerkle(16, [], True).root == get_empty_merkle_tree_hash(16) 35 | 36 | 37 | def test_check_membership(u): 38 | leaf_1 = b'\xff' * 31 + b'\x01' 39 | leaf_2 = b'\xff' * 31 + b'\x02' 40 | leaf_3 = b'\xff' * 31 + b'\x03' 41 | leaf_4 = b'\xff' * 31 + b'\x04' 42 | root = u.sha3(u.sha3(leaf_1 + leaf_2) + u.sha3(leaf_3 + leaf_4)) 43 | zeros_hashes = get_empty_merkle_tree_hash(2) 44 | for i in range(13): 45 | root = u.sha3(root + zeros_hashes[-32:]) 46 | zeros_hashes += u.sha3(zeros_hashes[-32:] + zeros_hashes[-32:]) 47 | left_proof = leaf_2 + u.sha3(leaf_3 + leaf_4) + zeros_hashes 48 | left_middle_proof = leaf_1 + u.sha3(leaf_3 + leaf_4) + zeros_hashes 49 | right_middle_proof = leaf_4 + u.sha3(leaf_1 + leaf_2) + zeros_hashes 50 | right_proof = leaf_3 + u.sha3(leaf_1 + leaf_2) + zeros_hashes 51 | fixed_merkle = FixedMerkle(16, [leaf_1, leaf_2, leaf_3, leaf_4], True) 52 | assert fixed_merkle.check_membership(leaf_1, 0, left_proof) is True 53 | assert fixed_merkle.check_membership(leaf_2, 1, left_middle_proof) is True 54 | assert fixed_merkle.check_membership(leaf_3, 2, right_middle_proof) is True 55 | assert fixed_merkle.check_membership(leaf_4, 3, right_proof) is True 56 | 57 | 58 | def test_create_membership_proof(): 59 | leaves = [b'a', b'b', b'c'] 60 | merkle = FixedMerkle(2, leaves) 61 | proof_1 = merkle.create_membership_proof(b'a') 62 | proof_2 = merkle.create_membership_proof(b'c') 63 | assert merkle.check_membership(b'a', 0, proof_1) is True 64 | assert merkle.check_membership(b'c', 2, proof_2) is True 65 | 66 | 67 | def test_is_member(): 68 | leaves = [b'a', b'b', b'c'] 69 | merkle = FixedMerkle(2, leaves, True) 70 | assert merkle.is_member(b'b') is True 71 | assert merkle.is_member(b'd') is False 72 | 73 | 74 | def test_non_member(): 75 | leaves = [b'a', b'b', b'c'] 76 | merkle = FixedMerkle(2, leaves, True) 77 | assert merkle.not_member(b'b') is False 78 | assert merkle.not_member(b'd') is True 79 | --------------------------------------------------------------------------------