Showing preview only (243K chars total). Download the full file or copy to clipboard to get everything.
Repository: BankEx/PlasmaETHexchange
Branch: master
Commit: 5c97f1f1b607
Files: 51
Total size: 228.3 KB
Directory structure:
gitextract_q8j4tzls/
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── Insomnia.json
├── LICENSE
├── README.md
├── app/
│ ├── config/
│ │ └── config.js
│ ├── endpoints/
│ │ ├── acceptAndSignTransaction.js
│ │ ├── acceptSignedTX.js
│ │ ├── acceptTransaction.js
│ │ ├── auxilary.js
│ │ ├── createTransactionToSign.js
│ │ ├── fundPlasma.js
│ │ ├── getBlockByNumber.js
│ │ ├── getTXsForAddress.js
│ │ ├── getTxByNumber.js
│ │ ├── getUTXOsForAddress.js
│ │ ├── getWithdrawsForAddress.js
│ │ ├── prepareProofForExpressWithdraw.js
│ │ └── withdraw.js
│ ├── helpers/
│ │ ├── checkSpendingTX.js
│ │ ├── createFundingTransaction.js
│ │ ├── createTxFromJson.js
│ │ ├── createWithdrawTxFromJson.js
│ │ ├── getAllTXsForAddress.js
│ │ ├── getAllUTXOsForAddress.js
│ │ ├── getAllWithdrawsForAddress.js
│ │ ├── getBlock.js
│ │ ├── getTX.js
│ │ ├── getUTXO.js
│ │ ├── hexDataToEncodedBytes.js
│ │ ├── prepareProofForTX.js
│ │ ├── processDepositEvent.js
│ │ ├── processExpressWithdrawMadeEvent.js
│ │ ├── processWithdrawFinalazedEvent.js
│ │ ├── processWithdrawStartedEvent.js
│ │ └── signatureChecks.js
│ └── miner.js
├── compile.js
├── contracts/
│ └── PlasmaParent.sol
├── lib/
│ ├── Block/
│ │ ├── block.js
│ │ └── blockHeader.js
│ ├── Tx/
│ │ ├── input.js
│ │ ├── output.js
│ │ └── tx.js
│ ├── dataStructureLengths.js
│ ├── merkle-tools.js
│ └── serialize.js
├── package.json
└── server.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
#VSCode settings
.vscode
db/
docker-compose.yml
privatekey.*
certificate.*
build/*
build/**
build
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct
## 1. Purpose
A primary goal of Plasma ETHexchange is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
We invite all those who participate in Plasma ETHexchange to help us create safe and positive experiences for everyone.
## 2. Open Source Citizenship
A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
## 3. Expected Behavior
The following behaviors are expected and requested of all community members:
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
* Exercise consideration and respect in your speech and actions.
* Attempt collaboration before conflict.
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
## 4. Unacceptable Behavior
The following behaviors are considered harassment and are unacceptable within our community:
* Violence, threats of violence or violent language directed against another person.
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
* Posting or displaying sexually explicit or violent material.
* Posting or threatening to post other people’s personally identifying information ("doxing").
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
* Inappropriate photography or recording.
* Inappropriate physical contact. You should have someone’s consent before touching them.
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
* Deliberate intimidation, stalking or following (online or in person).
* Advocating for, or encouraging, any of the above behavior.
* Sustained disruption of community events, including talks and presentations.
## 5. Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
## 6. Reporting Guidelines
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. pk@bankex.com.
[Reporting Guidelines](https://github.com/BankEx/PlasmaETHexchange/issues)
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
## 7. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify BankEx with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
[Policy](https://bankex.com/en/docs_terms-and-conditions)
## 8. Scope
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
## 9. Contact info
pk@bankex.com
## 10. License and attribution
This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)
================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute
Bug reports and pull requests from users is what keeps this project working.
## Basics
1. Create an issue and describe your idea
2. [Fork it](https://github.com/skywinder/github-changelog-generator/fork)
3. Create your feature branch (`git checkout -b my-new-feature`)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Publish the branch (`git push origin my-new-feature`)
6. Create a new Pull Request
## Checking your work
## Write documentation
This project has documentation in a few places:
### Introduction and usage
A friendly `README.md` written for many audiences.
### API documentation
================================================
FILE: ISSUE_TEMPLATE.md
================================================
To help us debug your issue please explain:
- What you were trying to do (and why)
- What happened (include command output)
- What you expected to happen
- Step-by-step reproduction instructions
# Features
Please replace this section with:
- a detailed description of your proposed feature
- the motivation for the feature
- how the feature would be relevant to at least 90% of Plasma users
================================================
FILE: Insomnia.json
================================================
{
"_type": "export",
"__export_format": 3,
"__export_date": "2017-11-10T15:02:45.828Z",
"__export_source": "insomnia.desktop.app:v5.10.1",
"resources": [
{
"_id": "wrk_72161b99e8634d158bc3b1e56cafd7e1",
"parentId": null,
"modified": 1510041196483,
"created": 1508971278767,
"name": "Plasma",
"description": "",
"_type": "workspace"
},
{
"_id": "env_0f55f98b561842268c9408cfd8671a2d",
"parentId": "wrk_72161b99e8634d158bc3b1e56cafd7e1",
"modified": 1508971278770,
"created": 1508971278770,
"name": "Base Environment",
"data": {},
"color": null,
"isPrivate": false,
"_type": "environment"
},
{
"_id": "fld_5cd6b9f637bd450180501c351f45b0f6",
"parentId": "wrk_72161b99e8634d158bc3b1e56cafd7e1",
"modified": 1510070686707,
"created": 1510070686707,
"name": "Plasma",
"description": "",
"environment": {},
"metaSortKey": -1510070686707,
"_type": "request_group"
},
{
"_id": "req_496880772a9a4bda9ab99e49a97c95d8",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510183267403,
"created": 1510070694280,
"url": "http://127.0.0.1:8000/fundPlasma",
"name": "Fund Plasma",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n\t\"toAddress\" : \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\"\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1510070694280,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_e42979a812a34f7a807fbe86041bc175",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510305375257,
"created": 1510184784598,
"url": "http://127.0.0.1:8000/plasmaBlock/1",
"name": "Get block by number",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509885060592.5,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_a33f04b400e74ce2a9aad20c85d71129",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510214454668,
"created": 1510189145263,
"url": "http://127.0.0.1:8000/sendAndSign",
"name": "Create and sign transaction",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n \"from\": \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\",\n \"txType\" : 1,\n \"inputs\": [\n\t\t\t{\n \"blockNumber\": 1,\n \"txNumber\": 0,\n \"outputNumber\" : 1\n\t\t\t}\n\t\t],\n \"outputs\": [{\n \"to\": \"0xcf78f18299eac0e0a238db7f4742ef433f98c85e\",\n \"amount\": \"50000000000000000\"\n },\n\t\t\t\t\t\t\t {\n \"to\": \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\",\n \"amount\": \"50000000000000000\"\n }]\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509977877436.25,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_84350a0776b6480587e064f4acf83887",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510215077681,
"created": 1510215037642,
"url": "http://127.0.0.1:8000/utxos/0xf62803ffaddda373d44b10bf6bb404909be0e66b",
"name": "Get UTXOs for address",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509792243748.75,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_04f8cc0c61f64bb38274c8ace0142ddc",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510215703043,
"created": 1510215690740,
"url": "http://127.0.0.1:8000/utxos/0xcf78f18299eac0e0a238db7f4742ef433f98c85e",
"name": "Get UTXOs for address 2",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509745835326.875,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_c0df9f648f0b4e388b33c0b798486941",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510267092689,
"created": 1510244074849,
"url": "http://127.0.0.1:8000/sendAndSign",
"name": "Create and sign withdraw",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n \"from\": \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\",\n \"txType\" : 3,\n \"inputs\": [\n\t\t\t{\n \"blockNumber\": 1,\n \"txNumber\": 0,\n \"outputNumber\" : 1\n\t\t\t}\n\t\t],\n \"outputs\": [{\n \"to\": \"0x0000000000000000000000000000000000000000\",\n \"amount\": \"100000000000000000\"\n }]\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509931469014.375,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_7cb4be30021041cd9f72c3bf0f569d68",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510305541259,
"created": 1510244291999,
"url": "http://127.0.0.1:8000/startWithdraw",
"name": "Start withdraw",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n \"from\": \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\",\n\t\t\"blockNumber\": 1,\n\t\t\"txNumber\": 0,\n\t\t\"txOutputNumber\":1\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509908264803.4375,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_299420c3887845f2a89f177d291ec288",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510267464215,
"created": 1510267369128,
"url": "http://127.0.0.1:8000/plasmaParent/blockHeader/1",
"name": "Get header by number",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509838652170.625,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_a0b739936b654841a59b89ac29b1629b",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510303494213,
"created": 1510267382301,
"url": "http://127.0.0.1:8000/plasmaParent/withdrawRecord/8589934592",
"name": "Get withdraw record by index",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509815447959.6875,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_c372e3e5120345718c276deaf2f21d4c",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510267625322,
"created": 1510267588233,
"url": "http://127.0.0.1:8000/ethereumBalance/0xf62803ffaddda373d44b10bf6bb404909be0e66b",
"name": "Get balance for address",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509803845854.2188,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_48d2d185224348bfa5007499e46aae0a",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510267853919,
"created": 1510267804004,
"url": "http://127.0.0.1:8000/ethereumBalance/0x8C05d5F202e9cab29a48b2DcBc2050bC3E20ae9c",
"name": "Get balance for contract",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509798044801.4844,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_8714811c360f449d8f1273b6e096aeda",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510306797552,
"created": 1510306761757,
"url": "http://127.0.0.1:8000/finalizeWithdrawExpress",
"name": "Finalize withdraw express",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n \"from\": \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\",\n\t\t\"blockNumber\": 2,\n\t\t\"txNumber\": 0,\n\t\t\"txOutputNumber\":1\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509896662697.9688,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_37bf10316d7d4417b97f4eb9413309e4",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510323439429,
"created": 1510323286024,
"url": "http://127.0.0.1:8000/plasmaParent/depositRecord/0x0000000000000000000000000000000000000000000000000000000200000000",
"name": "Get deposit record by index",
"description": "",
"method": "GET",
"body": {
"mimeType": "application/json",
"text": ""
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509809646906.9531,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_e7393b9c2f83435998ebf7c37b93241a",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510323871557,
"created": 1510323821224,
"url": "http://127.0.0.1:8000/startDepositWithdraw",
"name": "Start deposit withdraw",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n \"from\": \"0xf62803ffaddda373d44b10bf6bb404909be0e66b\",\n\t\t\"depositIndex\" : \"0x0000000000000000000000000000000000000000000000000000000200000000\"\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509902463750.7031,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
},
{
"_id": "req_7a1691fbd1bb4a59ba7008b64a39af3d",
"parentId": "fld_5cd6b9f637bd450180501c351f45b0f6",
"modified": 1510324284338,
"created": 1510324254548,
"url": "http://127.0.0.1:8000/challengeDepositWithdraw",
"name": "Challenge deposit withdraw",
"description": "",
"method": "POST",
"body": {
"mimeType": "application/json",
"text": "{\n \"blockNumber\": 1,\n\t\t\"txNumber\": 0,\n\t\t\"depositIndex\" : \"0x0000000000000000000000000000000000000000000000000000000200000000\"\n}"
},
"parameters": [],
"headers": [
{
"name": "Content-Type",
"value": "application/json",
"id": "pair_5175b94eb4d646a2a547812d2bf19ef4"
}
],
"authentication": {},
"metaSortKey": -1509899563224.336,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"_type": "request"
}
]
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 BANKEX - Proof-of-Asset Protocol
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
================================================
FILE: README.md
================================================

# Plasma protocol compatible ETH Exchange Platform
This is a wrapper and demo page.
Plasma like implementation and Parent smart contract [placed in BANKEX/PlasmaParentContract](https://github.com/BANKEX/PlasmaParentContract)*
### Plasma protocol DEMO:
https://plasma.bankex.com
### Source-code :octocat:
https://github.com/BankEx/plasma_client
# Public beta 1 features
The public beta will be deployed on Rinkeby network. Link: TBA
For Beta 1 only basic functions are enabled with non-optimized UI:
1. Deposit on the main chain
2. Transact in Plasma chain - split, merge, transfer, express withdraw
3. Only express withdraw is allowed for user experience - it's a two-step process:
Burn ETH in Plasma by submitting output to address 0x0
Submit proof of such transaction to the smart contract on the main chain to withdraw on the main chain
## Important features and limitations
- The function of Web3 to sign arbitrary data is used to proof your ownership of the address.
- #### Signature format is different from the signature format of Ethereum transactions, and any derived transaction can not be used to make fraudulent transaction on the main chain
- Sum of the outputs is strictly equal to the sum of the inputs
- As there is no standalone client to download and verify blocks, so there is no functionality to proof invalid transactions using the smart contract on the main chain. Wait until Beta 2
- UTXOs are not automatically merged into Beta 1
# Clarification
Initial implementation of a single server-side "Operator" service (with some extra functionality for testing) and v0.1 smart contract for Ethereum blockchain that can be used to enforce proper behavior in case of disputes.
### At the moment the groundwork is done (convenience functions + cryptography) in smart contract to continue writing required processes "Start -> (Challenge?) -> Complete".
#### Implemented challenges:
1. Deposit -> No deposit published in Plasma chain? -> Start withdrawing for deposit? -> Challenge deposit withdraw? -> Finalize
2. Deposit -> Do few transactions -> Start UTXO withdraw -> Express withdraw / Normal withdraw
3. Prove double spending on Plasma chain
4. Prove spending the same output on Plasma and successfully withdrawing it
5. Prove funding without deposit event or double funding TX of the same deposit
#### Required challenges:
1. Proof of incorrect TX: signature doesn't correspond to spent UTXOs, or amount is invalid. Special check for trying to spend auxiliary transaction output of funding transaction.
## What?
The task is to make blockchain transactions as fast as they can be applied virtually to any application.
## How?
One of the possible solutions is Plasma - the prominent upgrade to Ethereum blockchain. Our approach is based on this conception.
## Plasma Network Technical Concept
From a technical point of view Plasma blockchain - is just another blockchain, that can be efficiently settled to parent Ethereum chain and is well protected from the misbehavior of both Plasma operator and Plasma blockchain participants by smart-contract on Ethereum network.
Plasma chain itself has a straightforward structure with assets being undividable and transferred in full from the previous owner to the next one. The transaction has inputs and outputs, with few different types of transactions depending on the required function. For example, we propose type "Merge" to connect two inputs into one output in order to reduce the number of UTXOs that will be followed by the client. Full description will be given when the design is more stable. All chain logic is made using Ethereum crypto primitives - sha3, secp256k1 and 65-byte signatures allowing the use of ecrecover.
Block in Plasma network has a structure of Header: ```[BlockNumber, NumberOfTransactions, ParentHash, MerkleTreeRoot, PlasmaOperatorSignature]```, where
- `ParentHash` references to the previous block (by number)
- `MerkleTreeRoot` is root hash of a Merkle tree
- `NumberOfTransactions` transactions in this Plasma block and an array of transactions.
The header is submitted by Plasma network operator to the smart-contract on Ethereum chain. Blocks can only be sent one by one, with sequence numbering is enforced by contract. Any user of Ethereum network can deposit ETH to contract that will trigger, event and will allow Plasma network operator to make a funding transaction in a Plasma chain. Then users can freely transact in Plasma chain, with headers pushed to a parent contract in Ethereum.
When a user wants to settle one of his transactions to the main network, he initiates a withdraw on Ethereum network by providing the reference to the transaction (in the form of `BlockNumber`, `TxNumberInBlock`, `OutputNumberInTX`), full transaction and Merkle proof that this transaction was indeed included in that block. Parent contract checks a proof versus submitted root hash for this block. If validation was successful the process of withdrawing starts. After 24 hours it can be finalized. There is a particular kind of transaction in Plasma network that can speed up a process by efficiently burning the input (sending it to `0x0`). If this block is not published by the operator, withdrawing can go as usual.
## Technology in PoC
The concept is implemented using JS with conjunction on [Web3](https://github.com/ethereum/web3.js/) and [ethereumjs/testrpc](https://github.com/ethereumjs/testrpc) on a backend. For the sake of simplicity, all necessary functions are wrapped in REST API calls doing signatures on behalf of a predefined set of the address on a server. Further work will allow users to use wallet apps such as Metamask to initiate transactions in a Plasma network by making a signature on a client side and interacting with a parent contract on Ethereum network as usual.
## Why Plasma?
Here at BankEx, we believe in the efficiency of offloading of some transactions from Ethereum blockchain to Plasma chains especially if the proper incentive is present for Plasma operators to behave appropriately (such incentive can be presented in the form of competing with other operators for obtaining end-users). Another advantage is flexibility of Plasma chain implementation as long as it can be effectively cross-checked by contract on a parent chain. With the new cryptographic primitive added in Metropolis fork, one can extend our PoC implementation with transactions utilizing ring signatures of **zkSNARK**s for the privacy of end user.
## Getting Started
### Prerequisites
* Ubuntu or similar is preferable but can also be run on Mac OS X or Windows as well
* NodeJS version >= 8.9. Tested on 9.0
### Installing
#### Repository Cloning
```bash
git clone https://github.com/BankEx/PlasmaETHexchange.git
cd PlasmaETHexchange
```
#### Running
```bash
npm install
npm run server
```
### Usage
Backend ```localhost:8000/```
`Insomnia.json` is an Insomnia workspace file with various testing functions, play with it.
**!! May require some parameter changes in existing requests to comply with new output numbering.**
#### Addresses:
User 1: `0xf62803ffaddda373d44b10bf6bb404909be0e66b`
User 2: `0xcf78f18299eac0e0a238db7f4742ef433f98c85e`
Operator (oracle): `0x405aaaa4bdcda14a0af295f3669459b6b0fc8104`
### Basic Plasma API description
> Will be filled later
## Running the tests
> TBD
## Code of Conduct
To have a more open and welcoming community, BankEx adheres to a
[code of conduct](CODE_OF_CONDUCT.md).
## Communication :speaker:
Bug reports, feature requests, patches, well-wishes are always welcome!
- If you need help, [open an issue](https://github.com/BankEx/PlasmaETHexchange/issues/new).
- If you found a bug, [open an issue](https://github.com/BankEx/PlasmaETHexchange/issues/new).
- If you have a feature request, [open an issue](https://github.com/BankEx/PlasmaETHexchange/issues/new).
- If you want to contribute, see [Contributing](https://github.com/BankEx/PlasmaETHexchange#contributing-octocat) section.
## Contributing :octocat:
I'd love to see your ideas for improving this library!
* The best way to contribute is by submitting a pull request.
* Take an inspiration at [Plasma Official page](https://plasma.io/)
* [Fork](https://github.com/BankEx/PlasmaETHexchange/fork)
* Please read [CONTRIBUTING.md](https://github.com/BankEx/PlasmaETHexchange/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
## Authors
* **Alex Vlasov** - *Initial work* - [shamatar](https://github.com/shamatar)
See also the list of [contributors](https://github.com/BankEx/PlasmaETHexchange/contributors) who participated in this project.
## Acknowledgments
* Inspired by [Plasma protocol](https://plasma.io/)
* Given a push to our team on [ETHWaterloo - World's Largest Ethereum Hackathon](https://ethwaterloo.com/)
## License
PlasmaETHexchange is available under the MIT license. See the LICENSE file for more info.
================================================
FILE: app/config/config.js
================================================
module.exports = {
utxoPrefix : Buffer.from('utxo'),
blockPrefix : Buffer.from('blk'),
headerPrefix : Buffer.from('hdr'),
transactionPrefix :Buffer.from('tx'),
utxoIncludingAddressPrefix:Buffer.from('utxoaddr'),
txForAddressIndexPrefix:Buffer.from('txsaddr'),
withdrawsForAddressPrefix:Buffer.from('addrwithdraws'),
lastEventProcessedBlockPrefix:Buffer.from('lastEthBlock'),
lastSubmittedHeaderPrefix:Buffer.from('lastSubmHeader'),
depositIndexPrefix:Buffer.from("deps"),
makeAddressIndex: true,
makeTransactionIndexForAddress : true,
testOnRinkeby: true,
useSSL: false,
get port() {
if (this.useSSL) {
return 443
}
return 8000
},
get provider() {
if (this.testOnRinkeby){
return "http://127.0.0.1:8545"
} return "";
},
get deployedPlasmaContract(){
if (this.testOnRinkeby){
return "0xd8AC480331870c5764b5430F854926b1cfd1d8B1"
// return "0x158cb5485ea2e7fe03845d45c40c63469814bd9a"
return ""
} return "";
},
get plasmaOperatorAddress() {
if (this.testOnRinkeby){
return "0xe6877a4d8806e9a9f12eb2e8561ea6c1db19978d"
} return "0x405aaaa4bdcda14a0af295f3669459b6b0fc8104";
},
get plasmaOperatorPassword () {
if (this.testOnRinkeby){
return "plasmaTest"
} return "";
},
// plasmaOperatorPrivKeyHex: "0x4786e8e8cc2f7b6a5504add93505553010409a49fbc626001f2f34fd194ecfef",
// plasmaOperatorAddress: "0x405aaaa4bdcda14a0af295f3669459b6b0fc8104",
testAccounts: ["0xf62803ffaddda373d44b10bf6bb404909be0e66b", "0xcf78f18299eac0e0a238db7f4742ef433f98c85e"],
testPrivKeys: [Buffer.from("7e2abf9c3bcd5c08c6d2156f0d55764602aed7b584c4e95fa01578e605d4cd32", "hex"),
Buffer.from("0a201a3eb00242401c2d9fffefb0ed8a126281eeae98b4225fdc7265513285a2", "hex")],
blockTime: 30000
}
================================================
FILE: app/endpoints/acceptAndSignTransaction.js
================================================
// For demo purposes only
const Web3 = require('web3');
const validateSchema = require('jsonschema').validate;
const config = require("../config/config");
const testPrivKeys = config.testPrivKeys;
const testAccounts = config.testAccounts;
const ethUtil = require('ethereumjs-util');
const transactionSchema =
{
"from": {"type": "string", "minLength": 40, "maxLength": 42},
"txType" : {"type" : "integer", "minimum" : 1, "maximum" : 5},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"outputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"to": {"type": "string", "minLength": 40, "maxLength": 42},
"amount": {"type": "string", "minLength": 1},
}
}
}
}
module.exports = function(app, levelDB, web3) {
const createTxFromJSON = require('../helpers/createTxFromJson')(levelDB);
app.post('/sendAndSign', 'sendAndSignPlasmaTransaction', async function(req, res){
try{
if (!validateSchema(req.body, transactionSchema).valid) {
return res.json({error: true, reason: "invalid transaction"});
}
const from = ethUtil.addHexPrefix(req.body.from);
const idxInKeys = testAccounts.indexOf(from);
if (idxInKeys != 1 && idxInKeys != 0){
return res.json({error: true, reason: "invalid transaction"});
}
const tx = await createTxFromJSON(req.body);
let txRaw = ethUtil.bufferToHex(Buffer.concat(tx.clearRaw(false, false)));
let txHash = ethUtil.bufferToHex(tx.hash(false,false));
const signature = await web3.eth.sign(txRaw, from);
const signature2 = await web3.eth.sign(txHash, from);
tx.serializeSignature(signature);
if (!tx.validate()){
return res.json({error: true, reason: "invalid transaction"});
}
app.txQueueArray.push(tx);
console.log("Pushed new TX")
return res.json({error: false, status: "accepted"});
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
}
================================================
FILE: app/endpoints/acceptSignedTX.js
================================================
// For demo purposes only
const Web3 = require('web3');
const validateSchema = require('jsonschema').validate;
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const lengthConstants = require("../../lib/dataStructureLengths");
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require("../../lib/Tx/tx");
const transactionSchema =
{
// "from": {"type": "string", "minLength": 40, "maxLength": 42},
"txType" : {"type" : "integer", "minimum" : 1, "maximum" : 5},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"outputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"to": {"type": "string", "minLength": 40, "maxLength": 42},
"amount": {"type": "string", "minLength": 1},
}
}
},
"signature" : {"type" : "string", "minLength" :130, "maxLength" : 132},
"required": ["txType", "inputs", "signature"]
}
module.exports = function(app, levelDB, web3) {
const createTxFromJSON = require('../helpers/createTxFromJson')(levelDB);
const createWithdrawTxFromJSON = require('../helpers/createWithdrawTxFromJson')(levelDB);
const checkSpendingTX = require('../helpers/checkSpendingTX')(levelDB);
app.post('/sendSignedTX', 'endSignedTX', async function(req, res){
try{
if (!validateSchema(req.body, transactionSchema).valid) {
return res.json({error: true, reason: "invalid transaction"});
}
const txParams = req.body;
const signature = req.body.signature;
delete txParams.signature;
let tx;
if (req.body.txType == TxTypeMerge || req.body.txType == TxTypeSplit || req.body.txType == TxTypeTransfer) {
tx = await createTxFromJSON(req.body);
} else {
tx = await createWithdrawTxFromJSON(req.body);
}
tx.serializeSignature(signature);
const validSpending = await checkSpendingTX(tx);
assert(validSpending);
const txHash = tx.hash(false, false);
const pubKey = ethUtil.ecrecover(txHash, ethUtil.bufferToInt(tx.v), tx.r, tx.s);
const signedFromAddress = ethUtil.publicToAddress(pubKey).toString('hex');
console.log("Accepted TX from address 0x" + signedFromAddress);
const txIsValid = tx.validate(false);
assert(txIsValid);
app.txQueueArray.push(tx);
console.log("Pushed new TX")
const txJSONrepresentation = tx.toFullJSON(true);
return res.json({error: false,
tx: txJSONrepresentation});
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
}
================================================
FILE: app/endpoints/acceptTransaction.js
================================================
const Web3 = require('web3');
module.exports = function(app, levelDB, web3) {
app.post('/sendPlasmaTransaction', 'sendPlasmaTransaction', async function(req, res){
try{
const {blockNumber, txInBlock, assetId, to, v, r, s} = req.body
if (!blockNumber || !txInBlock || !assetId || !to || !v || !r || !s) {
return res.json({error: true, reason: "invalid transation"});
// next()
}
const tx = new PlasmaTransaction({blockNumber, txInBlock, assetId, to, v, r, s})
if (!tx.validate()){
return res.json({error: true, reason: "invalid transation"});
}
var unspentTxRaw
try{
const keyForUtxo = Buffer.concat([utxoPrefix, tx.blockNumber, tx.txInBlock]);
unspentTxRaw = await levelDB.get(keyForUtxo);
}
catch(err){
return res.json({error: true, reason: "invalid transation"});
}
const unspentTx = new PlasmaTransaction(sliceRawBufferForTx(unspentTxRaw))
if (!unspentTx.validate()){
return res.json({error: true, reason: "invalid transation"});
}
if (!unspentTx.to.equals(tx.getSenderAddress())){
return res.json({error: true, reason: "invalid transation"});
}
txParamsFIFO.push(tx);
console.log("Pushed new TX")
return res.json({error: false, status: "accepted"});
}
catch(error){
res.json({error: true, reason: "invalid transation"});
}
})
}
================================================
FILE: app/endpoints/auxilary.js
================================================
const Web3 = require('web3');
const BN = Web3.utils.BN;
const validateSchema = require('jsonschema').validate;
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const assert = require('assert');
// const testPrivKeys = config.testPrivKeys;
// const testAccounts = config.testAccounts;
// const plasmaOperatorPrivKeyHex = config.plasmaOperatorPrivKeyHex;
// const plasmaOperatorPrivKey = ethUtil.toBuffer(plasmaOperatorPrivKeyHex);
const plasmaOperatorAddress = config.plasmaOperatorAddress;
module.exports = function(app, levelDB, web3) {
app.get('/ethereumBalance/:address', 'balanceForAddress', async function(req, res){
try{
let addressString = req.params.address
addressString = ethUtil.addHexPrefix(addressString)
assert(ethUtil.isValidAddress(addressString))
const bal = await web3.eth.getBalance(addressString);
return res.json({balanceInWei:bal});
}
catch(error){
return res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/depositIndexes/:address', 'getDepositRecordsForAddress', async function(req, res){
try{
let addressString = req.params.address
addressString = ethUtil.addHexPrefix(addressString)
assert(ethUtil.isValidAddress(addressString))
const recordIndexes = await app.DeployedPlasmaContract.methods.depositRecordsForUser(addressString).call();
return res.json({depositIndexes:recordIndexes});
}
catch(error){
return res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/allDeposits/:address', 'getDepositRecordsForAddress', async function(req, res){
try{
let addressString = req.params.address
addressString = ethUtil.addHexPrefix(addressString)
assert(ethUtil.isValidAddress(addressString))
const recordIndexes = await app.DeployedPlasmaContract.methods.depositRecordsForUser(addressString).call();
const requests = recordIndexes.map((idx) => {
const index = Web3.utils.toBN(idx)
return app.DeployedPlasmaContract.methods.depositRecords(0,index).call();
})
let results = await Promise.all(requests)
return res.json({error: false, depositRecords:results});
}
catch(error){
return res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/depositRecord/:recordID', 'getDepositRecord', async function(req, res){
try{
let index = Web3.utils.toBN(req.params.recordID);
if (!index){
return res.json({error: true, reason: "invalid record index number"});
}
const record = await app.DeployedPlasmaContract.methods.depositRecords(0,index).call();
return res.json({error: false, record})
}
catch(error){
res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/lastSubmittedHeader', 'lastSubmittedHeader', async function(req, res){
try{
const headerNumber = await app.DeployedPlasmaContract.methods.lastBlockNumber().call();
return res.json({error: false, lastSubmittedHeader:headerNumber});
}
catch(error){
return res.json({error: true, reason: "invalid request"});
}
});
app.get('/plasmaParent/blockHeader/:blockNumber', 'getParentContractHeader', async function(req, res){
try{
const blockNumber = Web3.utils.toBN(req.params.blockNumber);
if (!blockNumber){
return res.json({error: true, reason: "invalid block number"});
}
var header = await app.DeployedPlasmaContract.methods.headers(blockNumber).call();
return res.json({error: false, header})
}
catch(error){
res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/withdrawIndexes/:address', 'getWithdrawRecordsForAddress', async function(req, res){
try{
let addressString = req.params.address
addressString = ethUtil.addHexPrefix(addressString)
assert(ethUtil.isValidAddress(addressString))
const recordIndexes = await app.DeployedPlasmaContract.methods.withdrawRecordsForUser(addressString).call();
return res.json({error: false, withdrawIndexes: recordIndexes});
}
catch(error){
return res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/withdrawRecord/:withdrawIndex', 'getParentContractHeader', async function(req, res){
try{
const withdrawIndex = Web3.utils.toBN(req.params.withdrawIndex);
if (!withdrawIndex){
return res.json({error: true, reason: "invalid withdraw index number"});
}
const record = await app.DeployedPlasmaContract.methods.withdrawRecords(0,withdrawIndex).call();
return res.json({error: false, withdrawRecord: record})
}
catch(error){
res.json({error: true, reason: "invalid address"});
}
});
app.get('/plasmaParent/allWithdraws/:address', 'getWithdrawRecordsForAddress', async function(req, res){
try{
let addressString = req.params.address
addressString = ethUtil.addHexPrefix(addressString)
assert(ethUtil.isValidAddress(addressString))
const recordIndexes = await app.DeployedPlasmaContract.methods.withdrawRecordsForUser(addressString).call();
const requests = recordIndexes.map((idx) => {
const index = Web3.utils.toBN(idx)
return app.DeployedPlasmaContract.methods.withdrawRecords(0,index).call();
})
let results = await Promise.all(requests)
return res.json({error: false, withdrawRecords:results});
}
catch(error){
return res.json({error: true, reason: "invalid address"});
}
});
}
================================================
FILE: app/endpoints/createTransactionToSign.js
================================================
// For demo purposes only
const Web3 = require('web3');
const validateSchema = require('jsonschema').validate;
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const lengthConstants = require("../../lib/dataStructureLengths");
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require("../../lib/Tx/tx");
const transactionSchema =
{
// "from": {"type": "string", "minLength": 40, "maxLength": 42},
"txType" : {"type" : "integer", "minimum" : 1, "maximum" : 5},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"outputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"to": {"type": "string", "minLength": 40, "maxLength": 42},
"amount": {"type": "string", "minLength": 1},
}
}
},
"required": ["txType", "inputs"]
}
module.exports = function(app, levelDB, web3) {
const createTxFromJSON = require('../helpers/createTxFromJson')(levelDB);
const createWithdrawTxFromJSON = require('../helpers/createWithdrawTxFromJson')(levelDB);
app.post('/createTX', 'createTX', async function(req, res){
try{
if (!validateSchema(req.body, transactionSchema).valid) {
return res.json({error: true, reason: "invalid transaction"});
}
let tx;
if (req.body.txType == TxTypeMerge || req.body.txType == TxTypeSplit || req.body.txType == TxTypeTransfer) {
tx = await createTxFromJSON(req.body);
} else {
tx = await createWithdrawTxFromJSON(req.body);
}
const txErrors = tx.validate(true);
assert(txErrors=='Invalid Signature');
const txRawNoNumber = Buffer.concat(tx.clearRaw(false, false));
const txJSONrepresentation = tx.toFullJSON(true);
const prefix = ethUtil.toBuffer("\x19Ethereum Signed Message:\n" + txRawNoNumber.length)
const stringToSign = prefix.toString('hex') + txRawNoNumber.toString('hex');
const txPersonalHash = tx.hash(false, false);
return res.json({error: false,
tx: txJSONrepresentation,
txHex: "0x"+txRawNoNumber.toString('hex'),
txPersonalHash: "0x"+txPersonalHash.toString('hex'),
txPersonalMessage: "0x"+stringToSign});
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
}
================================================
FILE: app/endpoints/fundPlasma.js
================================================
const Web3 = require('web3');
const BN = Web3.utils.BN;
const ethUtil = require('ethereumjs-util');
const config = require('../config/config');
const plasmaOperatorAddress = config.plasmaOperatorAddress;
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require('../../lib/Tx/tx');
module.exports = function(app, levelDB, web3) {
const DeployedPlasmaContract = app.DeployedPlasmaContract;
const processDepositEvent = require('../helpers/processDepositEvent')(app.txQueueArray);
const getBlockByNumber = require('../helpers/getBlock')(levelDB);
const prepareProofsForWithdraw = require('../helpers/prepareProofForTX')(levelDB);
const getTX = require('../helpers/getTX')(levelDB);
app.post('/fundPlasma', 'fundPlasma', async function(req, res){
try{
const {toAddress} = req.body
if (!toAddress) {
return res.json({error: true, reason: "invalid transaction"});
}
var result = await DeployedPlasmaContract.methods.deposit().send({from: toAddress, value: Web3.utils.toWei(0.1, 'ether'), gas: 3000000});
if (!result) {
return res.json({error: true, reason: "invalid transaction"});
}
const depositEvent = result.events.DepositEvent
// processDepositEvent(depositEvent)
return res.json({error: false, depositEvent});
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
app.post('/startDepositWithdraw', 'fundPlasma', async function(req, res){
try{
const {from, depositIndex} = req.body
if (!from || !depositIndex) {
return res.json({error: true, reason: "invalid transaction"});
}
const index = Web3.utils.toBN(depositIndex);
var result = await DeployedPlasmaContract.methods.startDepositWithdraw(index).send({from: from, gas: 3000000});
if (!result) {
return res.json({error: true, reason: "invalid transaction"});
}
const depositWithdrawStartedEvent = result.events.DepositWithdrawStartedEvent;
return res.json({error: false, depositWithdrawStartedEvent});
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
app.post('/challengeDepositWithdraw', 'fundPlasma', async function(req, res){
try{
const {blockNumber, txNumber, depositIndex} = req.body
if (!blockNumber || !depositIndex) {
return res.json({error: true, reason: "invalid transaction"});
}
const index = Web3.utils.toBN(depositIndex);
const txNumberInBlock = txNumber;
const tx = await getTX(blockNumber, txNumberInBlock);
if (!tx) {
return res.json({error: true, reason: "invalid transaction"});
}
assert(tx.transactionTypeUInt() == TxTypeFund)
const preparedProof = await prepareProofsForWithdraw(blockNumber, txNumberInBlock);
const result = await app.DeployedPlasmaContract.methods.challengeDepositWithdraw(index, preparedProof.blockNumber,
// preparedProof.txNumberInBlock,
preparedProof.tx, preparedProof.merkleProof ).send({from:plasmaOperatorAddress, gas: 3.6e6});
const depositWithdrawChallengedEvent = result.events.DepositWithdrawChallengedEvent;
const response = {error: false, depositWithdrawChallengedEvent};
return res.json(response);
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
}
================================================
FILE: app/endpoints/getBlockByNumber.js
================================================
const Web3 = require('web3');
module.exports = function(app, levelDB, web3) {
const getBlockByNumber = require('../helpers/getBlock')(levelDB);
app.get('/plasmaBlock/:id', 'getBlockByNumber', async function(req, res){
try{
const blockNumber = parseInt(req.params.id);
if (!blockNumber){
return res.json({error: true, reason: "invalid block number"});
}
const block = await getBlockByNumber(blockNumber);
return res.json(block.toFullJSON(true))
}
catch(error){
return res.json({error: true, reason: "invalid block number"});
}
});
}
================================================
FILE: app/endpoints/getTXsForAddress.js
================================================
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const assert = require('assert')
module.exports = function(app, levelDB, web3) {
const getTXsforAddress = require('../helpers/getAllTXsForAddress')(levelDB);
app.get('/txs/:address', 'getTXsByAddress', async function(req, res, next) {
try{
addressString = req.params.address
assert(ethUtil.isValidAddress(addressString))
addressString = ethUtil.addHexPrefix(addressString)
getTXsforAddress(addressString, function(err, txs) {
if (err){
console.log(err)
res.json({error: true, reason: "invalid address"});
next()
}
res.json({error: false, address: addressString, txs})
next()
return
})
}
catch(error) {
res.json({error: true, reason: "invalid address"});
next()
}
});
}
================================================
FILE: app/endpoints/getTxByNumber.js
================================================
const Web3 = require('web3');
module.exports = function(app, levelDB, web3) {
const getTX = require('../helpers/getTX')(levelDB);
app.get('/plasmaTX/:blockNumber/:txNumberInBlock', 'getBlockByNumber', async function(req, res){
try{
const blockNumber = parseInt(req.params.blockNumber);
const txNumberInBlock = parseInt(req.params.txNumberInBlock);
if (blockNumber == undefined || txNumberInBlock == undefined){
return res.json({error: true, reason: "invalid transaction number"});
}
let tx = await getTX(blockNumber, txNumberInBlock);
tx = tx.toFullJSON(true)
tx.header['blockNumber'] = blockNumber;
return res.json(tx)
}
catch(error){
return res.json({error: true, reason: "invalid block number"});
}
});
}
================================================
FILE: app/endpoints/getUTXOsForAddress.js
================================================
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const assert = require('assert')
module.exports = function(app, levelDB, web3) {
const getUTXOforAddress = require('../helpers/getAllUTXOsForAddress')(levelDB);
app.get('/utxos/:address', 'getUtxosByAddress', async function(req, res, next) {
try{
addressString = req.params.address
assert(ethUtil.isValidAddress(addressString))
addressString = ethUtil.addHexPrefix(addressString)
getUTXOforAddress(addressString, function(err, utxos) {
if (err){
console.log(err)
res.json({error: true, reason: "invalid address"});
next()
}
res.json({error: false, address: addressString, utxos})
next()
return
})
}
catch(error) {
res.json({error: true, reason: "invalid address"});
next()
}
});
}
================================================
FILE: app/endpoints/getWithdrawsForAddress.js
================================================
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const assert = require('assert')
module.exports = function(app, levelDB, web3) {
const getWithdrawsforAddress = require('../helpers/getAllWithdrawsForAddress')(levelDB);
app.get('/withdraws/:address', 'getWithdrawssByAddress', async function(req, res, next) {
try{
addressString = req.params.address
assert(ethUtil.isValidAddress(addressString))
addressString = ethUtil.addHexPrefix(addressString)
getWithdrawsforAddress(addressString, function(err, txs) {
if (err){
console.log(err)
res.json({error: true, reason: "invalid address"});
next()
}
res.json({error: false, address: addressString, txs})
next()
return
})
}
catch(error) {
res.json({error: true, reason: "invalid address"});
next()
}
});
}
================================================
FILE: app/endpoints/prepareProofForExpressWithdraw.js
================================================
//demo purposes only
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require('../../lib/Tx/tx');
const encodeForRemix = require("../helpers/hexDataToEncodedBytes");
module.exports = function(app, levelDB, web3) {
const getBlockByNumber = require('../helpers/getBlock')(levelDB);
const getTX = require('../helpers/getTX')(levelDB);
const prepareProofsForWithdraw = require('../helpers/prepareProofForTX')(levelDB);
app.post('/prepareProofForExpressWithdraw', 'prepareProofForExpressWithdraw', async function(req, res){
try{
const {blockNumber, txNumber} = req.body
if (!blockNumber || txNumber==undefined) {
return res.json({error: true, reason: "invalid transaction"});
}
const txNumberInBlock = txNumber;
const blockNumberBuffer = ethUtil.toBuffer(blockNumber)
const txNumberInBlockBuffer = ethUtil.toBuffer(txNumberInBlock)
const tx = await getTX(blockNumber, txNumberInBlock);
if (!tx) {
return res.json({error: true, reason: "invalid transaction"});
}
assert(tx.transactionTypeUInt() == TxTypeWithdraw)
const preparedProof = await prepareProofsForWithdraw(blockNumber, txNumberInBlock);
return res.json({error: false,
proof: {
blockNumber: preparedProof.blockNumber,
txNumber:preparedProof.txNumberInBlock,
tx: preparedProof.tx,
merkleProof: preparedProof.merkleProof
}
})
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
}
================================================
FILE: app/endpoints/withdraw.js
================================================
//demo purposes only
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require('../../lib/Tx/tx');
const encodeForRemix = require("../helpers/hexDataToEncodedBytes");
module.exports = function(app, levelDB, web3) {
const getBlockByNumber = require('../helpers/getBlock')(levelDB);
const getTX = require('../helpers/getTX')(levelDB);
const prepareProofsForWithdraw = require('../helpers/prepareProofForTX')(levelDB);
app.post('/startWithdraw', 'startWithdraw', async function(req, res){
try{
const {blockNumber, txNumber, txOutputNumber, from} = req.body
if (!blockNumber || !from) {
return res.json({error: true, reason: "invalid transaction"});
}
const txNumberInBlock = txNumber;
const tx = await getTX(blockNumber, txNumberInBlock);
if (!tx) {
return res.json({error: true, reason: "invalid transaction"});
}
assert(tx.transactionTypeUInt() == TxTypeSplit ||
tx.transactionTypeUInt() == TxTypeMerge ||
tx.transactionTypeUInt() == TxTypeTransfer ||
tx.transactionTypeUInt() == TxTypeFund)
const preparedProof = await prepareProofsForWithdraw(blockNumber, txNumberInBlock);
const result = await app.DeployedPlasmaContract.methods.startWithdraw(preparedProof.blockNumber,
preparedProof.txNumberInBlock, req.body.txOutputNumber, preparedProof.tx, preparedProof.merkleProof ).send({from:from, gas: 3.6e6});
const acceptanceEvent = result.events.WithdrawRequestAcceptedEvent;
const withdrawStartedEvent = result.events.WithdrawStartedEvent;
console.log(acceptanceEvent);
console.log(withdrawStartedEvent);
const response = {error: false, status: "accepted",
withdrawIndex: acceptanceEvent.returnValues._withdrawIndex,
acceptanceEvent,
withdrawStartedEvent}
return res.json(response);
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
app.post('/finalizeWithdrawExpress', 'finalizeWithdrawExpress', async function(req, res){
try{
const {blockNumber, txNumber, from} = req.body
if (!blockNumber || !from) {
return res.json({error: true, reason: "invalid transaction"});
}
const txNumberInBlock = txNumber;
const blockNumberBuffer = ethUtil.toBuffer(blockNumber)
const txNumberInBlockBuffer = ethUtil.toBuffer(txNumberInBlock)
const tx = await getTX(blockNumber, txNumberInBlock);
if (!tx) {
return res.json({error: true, reason: "invalid transaction"});
}
assert(tx.transactionTypeUInt() == TxTypeWithdraw)
const preparedProof = await prepareProofsForWithdraw(blockNumber, txNumberInBlock);
const result = await app.DeployedPlasmaContract.methods.makeWithdrawExpress(preparedProof.blockNumber,
preparedProof.txNumberInBlock, preparedProof.tx, preparedProof.merkleProof ).send({from:from, gas: 3.6e6});
const withdrawFinalizedEvent = result.events.WithdrawFinalizedEvent;
console.log(withdrawFinalizedEvent);
const response = {error: false, status: "accepted",
withdrawIndex: withdrawFinalizedEvent.returnValues._withdrawIndex,
withdrawFinalizedEvent}
return res.json(response);
}
catch(error){
res.json({error: true, reason: "invalid transaction"});
}
});
app.post('/finalizeWithdraw', 'finalizeWithdraw', async function(req, res){
try{
const {withdrawIndex, from} = req.body
if (!from || !withdrawIndex) {
return res.json({error: true, reason: "invalid request"});
}
await app.jump("2 days")();
var result = await DeployedPlasmaContract.methods.finalizeWithdraw(inEthereumBlock, withdrawIndex).send({from: from, gas: 3.6e6});
const finalizationEvent = result.events.WithdrawFinalizedEvent;
const response = {error: false, status: "accepted",
withdrawIndex: withdrawFinalizedEvent.returnValues._withdrawIndex,
withdrawFinalizedEvent}
return res.json(response);
}
catch(error){
res.json({error: true, reason: "invalid request"});
}
});
}
================================================
FILE: app/helpers/checkSpendingTX.js
================================================
const config = require("../config/config");
const blockPrefix = config.blockPrefix;
const utxoPrefix = config.utxoPrefix;
const utxoIncludingAddressPrefix = config.utxoIncludingAddressPrefix;
const makeAddressIndex = config.makeAddressIndex;
const headerPrefix = config.headerPrefix;
const transactionPrefix=config.transactionPrefix;
const blockTime = config.blockTime;
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
const Web3 = require('web3');
const Block = require('../../lib/Block/block');
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const plasmaOperatorAddress = config.plasmaOperatorAddress;
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require('../../lib/Tx/tx');
const {TransactionInput} = require('../../lib/Tx/input');
const {TransactionOutput} = require('../../lib/Tx/output');
module.exports = function(levelDB) {
return async function checkSpendingTX(spendingTx) {
if (spendingTx.getKey() == "000000000000000000") {
return config.plasmaOperatorAddress === ethUtil.bufferToHex(spendingTx.getSenderAddress());
} else {
try{
const senderAddress = spendingTx.getSenderAddress();
var amountFromInputs = new BN(0)
var amountFromOutputs = new BN(0)
for (let inpIndex of [0,1]) {
const input = spendingTx.getTransactionInput(inpIndex)
if (input && typeof input != "undefined") {
const keyForUTXO = Buffer.concat([utxoPrefix, input.blockNumber, input.txNumberInBlock, input.outputNumberInTransaction]);
const unspentTxRaw = await levelDB.get(keyForUTXO)
const unspentTx = TransactionOutput.prototype.initFromBinaryBlob(unspentTxRaw);
if (!unspentTx){
return false
}
if (spendingTx.transactionTypeUInt() == TxTypeMerge) {
if (!unspentTx.to.equals(senderAddress)) {
if (!senderAddress.equals(ethUtil.toBuffer(config.plasmaOperatorAddress))) {
return false
}
}
} else {
if (!unspentTx.to.equals(senderAddress)){
return false
}
}
amountFromInputs = amountFromInputs.add(unspentTx.value)
}
}
for (let outIndex of [0,1]) {
const output = spendingTx.getTransactionOutput(outIndex);
if (output && typeof output != "undefined" && !(output.outputNumberInTransaction.equals(Buffer.from('ff', 'hex'))) ) {
amountFromOutputs = amountFromOutputs.add(output.value)
}
}
return amountFromInputs.eq(amountFromOutputs);
}
catch(error){
return false;
}
}
return false;
}
}
================================================
FILE: app/helpers/createFundingTransaction.js
================================================
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
// const plasmaOperatorPrivKeyHex = config.plasmaOperatorPrivKeyHex;
// const plasmaOperatorPrivKey = ethUtil.toBuffer(plasmaOperatorPrivKeyHex);
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForTypes} = require("../../lib/Tx/tx");
const {TransactionInput, TransactionInputLength} = require("../../lib/Tx/input");
const {TransactionOutput, TransactionOutputLength} = require("../../lib/Tx/output");
const dummyInput = new TransactionInput();
const dummyOutput = new TransactionOutput();
module.exports = function createFundingTransaction(toAddressString, amountBN, depositIndexBN) {
const amountBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(amountBN),txAmountLength)
const inputParams = {
blockNumber: '0x' + '00'.repeat(blockNumberLength),
txNumberInBlock: '0x' +'00'.repeat(txNumberLength),
// assetID: ethUtil.setLengthLeft(ethUtil.bufferToHex(ethUtil.toBuffer(asset)), 4),
outputNumberInTransaction: '0x00',
amountBuffer
}
const input = new TransactionInput(inputParams);
const outputParams = {
to: ethUtil.addHexPrefix(toAddressString),
// assetID: ethUtil.setLengthLeft(ethUtil.bufferToHex(ethUtil.toBuffer(asset)), 4),
outputNumberInTransaction: '0x00',
amountBuffer
}
const output = new TransactionOutput(outputParams);
const txTypeBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(TxTypeFund)),txTypeLength)
const depositRecordParams = {
to: ethUtil.addHexPrefix(toAddressString),
outputNumberInTransaction: '0xff',
amountBuffer: ethUtil.setLengthLeft(ethUtil.toBuffer(depositIndexBN),txAmountLength)
}
const auxOutput = new TransactionOutput(depositRecordParams);
const txParams = {
transactionType: txTypeBuffer,
inputNum0: Buffer.concat(input.raw),
outputNum0: Buffer.concat(output.raw),
outputNum1: Buffer.concat(auxOutput.raw)
}
const tx = new PlasmaTransaction(txParams);
return tx;
// tx.sign(plasmaOperatorPrivKey);
// return tx;
}
================================================
FILE: app/helpers/createTxFromJson.js
================================================
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const plasmaOperatorPrivKeyHex = config.plasmaOperatorPrivKeyHex;
const plasmaOperatorPrivKey = ethUtil.toBuffer(plasmaOperatorPrivKeyHex);
const Web3 = require('web3');
const BN = Web3.utils.BN;
const validateSchema = require('jsonschema').validate;
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require("../../lib/Tx/tx");
const {TransactionInput, TransactionInputLength} = require("../../lib/Tx/input");
const {TransactionOutput, TransactionOutputLength} = require("../../lib/Tx/output");
const dummyInput = new TransactionInput();
const dummyOutput = new TransactionOutput();
const transactionSchemaNoSignature =
{
"txType" : {"type" : "integer", "minimum" : 1, "maximum" : 5},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"outputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"to": {"type": "string", "minLength": 40, "maxLength": 42},
"amount": {"type": "string", "minLength": 1},
}
}
},
"required": ["txType", "inputs", "outputs"]
}
const transactionSchemaWithSignature = {
"txType" : {"type" : "integer", "minimum" : 1, "maximum" : 5},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"outputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"to": {"type": "string", "minLength": 40, "maxLength": 42},
"amount": {"type": "string", "minLength": 1},
}
}
},
"v": {"type": "string", "minLength": 2, "maxLength": 2},
"r": {"type": "string", "minLength": 44, "maxLength": 44},
"s": {"type": "string", "minLength": 44, "maxLength": 44},
"required": ["txType", "inputs", "outputs", "v", "r", "s"]
}
module.exports = function (levelDB) {
const getUTXO = require('./getUTXO')(levelDB);
const getTX = require('./getTX')(levelDB);
return async function (transactionJSON) {
try{
if (!validateSchema(transactionJSON, transactionSchemaNoSignature).valid) {
return null
}
const txType = transactionJSON.txType;
const numInputs = transactionJSON.inputs.length;
const numOutputs = transactionJSON.outputs.length
if (numInputs != NumInputsForType[txType] || numOutputs != NumOutputsForType[txType]){
return null;
}
let inputsTotalValue = new BN(0);
let outputsTotalValue = new BN(0);
const txParams = {}
let inputCounter = 0;
// const inputs = []
for (let inputJSON of transactionJSON.inputs) {
const unspentOutput = await getUTXO(inputJSON.blockNumber, inputJSON.txNumber, inputJSON.outputNumber);
if (!unspentOutput) {
return null;
}
const blockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(inputJSON.blockNumber)),blockNumberLength)
const txNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(inputJSON.txNumber)),txNumberLength)
const txOutputNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(inputJSON.outputNumber)),txOutputNumberLength)
const inputParams = {
blockNumber: blockNumberBuffer,
txNumberInBlock: txNumberBuffer,
outputNumberInTransaction: txOutputNumberBuffer,
amountBuffer: unspentOutput.amountBuffer
}
const input = new TransactionInput(inputParams);
// inputs.push(input)
inputsTotalValue = inputsTotalValue.add(unspentOutput.value);
txParams["inputNum"+inputCounter]=Buffer.concat(input.raw);
inputCounter++;
}
let outputCounter = 0;
for (let outputJSON of transactionJSON.outputs) {
const outputNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(outputCounter)),txOutputNumberLength)
const outputValue = new BN(outputJSON.amount);
if (outputValue.lte(0)) {
return null;
}
outputsTotalValue = outputsTotalValue.add(outputValue);
const addr = ethUtil.addHexPrefix(outputJSON.to.toLowerCase());
if (addr == undefined) {
return null;
}
const outputParams = {
to: addr,
// assetID: ethUtil.setLengthLeft(ethUtil.bufferToHex(ethUtil.toBuffer(asset)), 4),
outputNumberInTransaction: outputNumberBuffer,
amountBuffer: ethUtil.setLengthLeft(ethUtil.toBuffer(outputValue),txAmountLength)
}
const transactionOutput = new TransactionOutput(outputParams);
txParams["outputNum"+outputCounter] = Buffer.concat(transactionOutput.raw);
outputCounter++
}
if (!outputsTotalValue.eq(inputsTotalValue)) {
return null;
}
// if (txType == TxTypeMerge) {
// if (! inputs[0].to.equals(inputs[1].to)){
// return null;
// }
// }
const txTypeBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(txType)), txTypeLength)
txParams.transactionType = txTypeBuffer;
const tx = new PlasmaTransaction(txParams);
return tx;
}
catch(err){
return null;
}
}
}
================================================
FILE: app/helpers/createWithdrawTxFromJson.js
================================================
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const plasmaOperatorPrivKeyHex = config.plasmaOperatorPrivKeyHex;
const plasmaOperatorPrivKey = ethUtil.toBuffer(plasmaOperatorPrivKeyHex);
const Web3 = require('web3');
const BN = Web3.utils.BN;
const validateSchema = require('jsonschema').validate;
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require("../../lib/Tx/tx");
const {TransactionInput, TransactionInputLength} = require("../../lib/Tx/input");
const {TransactionOutput, TransactionOutputLength} = require("../../lib/Tx/output");
const dummyInput = new TransactionInput();
const dummyOutput = new TransactionOutput();
const transactionSchemaNoSignature =
{
"txType" : {"type" : "integer", "minimum" : TxTypeWithdraw, "maximum" : TxTypeWithdraw},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"required": ["txType", "inputs"]
}
const transactionSchemaWithSignature = {
"txType" : {"type" : "integer", "minimum" : TxTypeWithdraw, "maximum" : TxTypeWithdraw},
"inputs": {
"type": "array",
"items": {"type": "object",
"properties": {
"blockNumber": {"type": "integer", "minimum": 1},
"txNumber": {"type": "integer", "minimum": 1},
"outputNumber" : {"type": "integer", "minimum": 1}
}
}
},
"v": {"type": "string", "minLength": 2, "maxLength": 2},
"r": {"type": "string", "minLength": 44, "maxLength": 44},
"s": {"type": "string", "minLength": 44, "maxLength": 44},
"required": ["txType", "inputs", "v", "r", "s"]
}
module.exports = function (levelDB) {
const getUTXO = require('./getUTXO')(levelDB);
const getTX = require('./getTX')(levelDB);
return async function (transactionJSON) {
try{
if (!validateSchema(transactionJSON, transactionSchemaNoSignature).valid) {
return null
}
const txType = transactionJSON.txType;
assert(txType == TxTypeWithdraw);
const numInputs = transactionJSON.inputs.length;
if (numInputs != NumInputsForType[txType]){
return null;
}
let inputsTotalValue = new BN(0);
let outputsTotalValue = new BN(0);
const txParams = {}
let inputCounter = 0;
const inputs = []
for (let inputJSON of transactionJSON.inputs) {
const unspentOutput = await getUTXO(inputJSON.blockNumber, inputJSON.txNumber, inputJSON.outputNumber);
if (!unspentOutput) {
return null;
}
const blockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(inputJSON.blockNumber)),blockNumberLength)
const txNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(inputJSON.txNumber)),txNumberLength)
const txOutputNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(inputJSON.outputNumber)),txOutputNumberLength)
const inputParams = {
blockNumber: blockNumberBuffer,
txNumberInBlock: txNumberBuffer,
outputNumberInTransaction: txOutputNumberBuffer,
amountBuffer: unspentOutput.amountBuffer
}
const input = new TransactionInput(inputParams);
inputs.push(input)
inputsTotalValue = inputsTotalValue.add(unspentOutput.value);
txParams["inputNum"+inputCounter]=Buffer.concat(input.raw);
inputCounter++;
}
let outputCounter = 0;
const outputNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(outputCounter)),txOutputNumberLength)
const outputValue = inputsTotalValue;
if (outputValue.lte(0)) {
return null;
}
outputsTotalValue = outputsTotalValue.add(outputValue);
const outputParams = {
to: ethUtil.setLengthLeft(ethUtil.toBuffer("0x00"),txToAddressLength),
// assetID: ethUtil.setLengthLeft(ethUtil.bufferToHex(ethUtil.toBuffer(asset)), 4),
outputNumberInTransaction: outputNumberBuffer,
amountBuffer: ethUtil.setLengthLeft(ethUtil.toBuffer(outputValue),txAmountLength)
}
const transactionOutput = new TransactionOutput(outputParams);
txParams["outputNum"+outputCounter] = Buffer.concat(transactionOutput.raw);
if (!outputsTotalValue.eq(inputsTotalValue)) {
return null;
}
const txTypeBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(txType)), txTypeLength)
txParams.transactionType = txTypeBuffer;
const tx = new PlasmaTransaction(txParams);
return tx;
}
catch(err){
return null;
}
}
}
================================================
FILE: app/helpers/getAllTXsForAddress.js
================================================
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const {blockPrefix,
utxoIncludingAddressPrefix,
utxoPrefix,
makeAddressIndex,
txForAddressIndexPrefix} = config;
const {TransactionOutput} = require("../../lib/Tx/output");
const {TransactionInput} = require("../../lib/Tx/input");
const {BlockHeader} = require("../../lib/Block/blockHeader");
const assert = require('assert');
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
module.exports = function(levelDB) {
if (config.makeTransactionIndexForAddress) {
return getTXsAddressWithIndex;
} else {
return getTXsforAddress;
}
async function getTXsforAddress(addressString, cb) {
addressString = ethUtil.addHexPrefix(addressString)
if (!ethUtil.isValidAddress(addressString)){
cb(true, null);
}
address = ethUtil.toBuffer(addressString);
const utxos = [];
const start = Buffer.concat([utxoPrefix,
Buffer.alloc(blockNumberLength),
Buffer.alloc(txNumberLength),
Buffer.alloc(txOutputNumberLength)])
const stop = Buffer.concat([utxoPrefix,
Buffer.from("ff".repeat(blockNumberLength), 'hex'),
Buffer.from("ff".repeat(txNumberLength), 'hex'),
Buffer.from("ff".repeat(txOutputNumberLength), 'hex')])
const sliceStart = utxoPrefix.length;
levelDB.createReadStream({gte: start,
lte: stop,
reversed:true})
.on('data', function (data) {
if (data.value.slice(0,txToAddressLength).equals(address)) {
const out = TransactionOutput.prototype.initFromBinaryBlob(data.value);
toReturn = {};
toReturn["blockNumber"] = ethUtil.bufferToInt(data.key.slice(sliceStart, sliceStart + blockNumberLength))
toReturn["txNumberInBlock"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength, sliceStart + blockNumberLength + txNumberLength))
toReturn["outputNumberInTransaction"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength + txNumberLength, sliceStart + blockNumberLength + txNumberLength + txOutputNumberLength))
toReturn["value"] = out.value.toString(10);
toReturn['output'] = out;
utxos.push(toReturn);
}
})
.on('error', function (err) {
console.log('Oh my!', err)
cb(err, null)
})
.on('close', function () {
// cb(null, utxos)
// console.log('Stream closed')
})
.on('end', function () {
cb(null, utxos)
// console.log('Stream ended')
})
}
async function getTXsAddressWithIndex(addressString, cb) {
addressString = ethUtil.addHexPrefix(addressString)
if (!ethUtil.isValidAddress(addressString)){
cb(true, null);
}
address = ethUtil.toBuffer(addressString);
const utxos = [];
const start = Buffer.concat([txForAddressIndexPrefix,
address,
Buffer.alloc(blockNumberLength),
Buffer.alloc(txNumberLength)])
const stop = Buffer.concat([txForAddressIndexPrefix,
address,
Buffer.from("ff".repeat(blockNumberLength), 'hex'),
Buffer.from("ff".repeat(txNumberLength), 'hex'),])
const sliceStart = txForAddressIndexPrefix.length + address.length;
levelDB.createReadStream({gte: start,
lte: stop,
reversed:true})
.on('data', function (data) {
toReturn = {};
toReturn["blockNumber"] = ethUtil.bufferToInt(data.key.slice(sliceStart, sliceStart + blockNumberLength))
toReturn["txNumberInBlock"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength, sliceStart + blockNumberLength + txNumberLength))
utxos.push(toReturn);
})
.on('error', function (err) {
console.log('Oh my!', err)
cb(err, null)
})
.on('close', function () {
// cb(null, utxos)
// console.log('Stream closed')
})
.on('end', function () {
cb(null, utxos)
// console.log('Stream ended')
})
}
}
================================================
FILE: app/helpers/getAllUTXOsForAddress.js
================================================
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const blockPrefix = config.blockPrefix;
const utxoIncludingAddressPrefix = config.utxoIncludingAddressPrefix;
const utxoPrefix = config.utxoPrefix;
const makeAddressIndex = config.makeAddressIndex;
const {TransactionOutput} = require("../../lib/Tx/output");
const {TransactionInput} = require("../../lib/Tx/input");
const {BlockHeader} = require("../../lib/Block/blockHeader");
const assert = require('assert');
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
module.exports = function(levelDB) {
if (makeAddressIndex) {
return getUTXOforAddressWithIndex;
} else {
return getUTXOforAddress;
}
async function getUTXOforAddress(addressString, cb) {
addressString = ethUtil.addHexPrefix(addressString)
if (!ethUtil.isValidAddress(addressString)){
cb(true, null);
}
address = ethUtil.toBuffer(addressString);
const utxos = [];
const start = Buffer.concat([utxoPrefix,
Buffer.alloc(blockNumberLength),
Buffer.alloc(txNumberLength),
Buffer.alloc(txOutputNumberLength)])
const stop = Buffer.concat([utxoPrefix,
Buffer.from("ff".repeat(blockNumberLength), 'hex'),
Buffer.from("ff".repeat(txNumberLength), 'hex'),
Buffer.from("ff".repeat(txOutputNumberLength), 'hex')])
const sliceStart = utxoPrefix.length;
levelDB.createReadStream({gte: start,
lte: stop,
reversed:true})
.on('data', function (data) {
if (data.value.slice(0,txToAddressLength).equals(address)) {
const out = TransactionOutput.prototype.initFromBinaryBlob(data.value);
toReturn = {};
toReturn["blockNumber"] = ethUtil.bufferToInt(data.key.slice(sliceStart, sliceStart + blockNumberLength))
toReturn["txNumberInBlock"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength, sliceStart + blockNumberLength + txNumberLength))
toReturn["outputNumberInTransaction"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength + txNumberLength, sliceStart + blockNumberLength + txNumberLength + txOutputNumberLength))
toReturn["to"] = ethUtil.toChecksumAddress(ethUtil.bufferToHex(out.to))
toReturn["value"] = out.value.toString(10);
// toReturn["to"] = ethUtil.toChecksumAddress(ethUtil.bufferToHex(out.to))
// toReturn['output'] = out;
utxos.push(toReturn);
}
})
.on('error', function (err) {
console.log('Oh my!', err)
cb(err, null)
})
.on('close', function () {
// cb(null, utxos)
// console.log('Stream closed')
})
.on('end', function () {
cb(null, utxos)
// console.log('Stream ended')
})
}
async function getUTXOforAddressWithIndex(addressString, cb) {
addressString = ethUtil.addHexPrefix(addressString)
if (!ethUtil.isValidAddress(addressString)){
cb(true, null);
}
address = ethUtil.toBuffer(addressString);
const utxos = [];
const start = Buffer.concat([utxoIncludingAddressPrefix,
address,
Buffer.alloc(blockNumberLength),
Buffer.alloc(txNumberLength),
Buffer.alloc(txOutputNumberLength)])
const stop = Buffer.concat([utxoIncludingAddressPrefix,
address,
Buffer.from("ff".repeat(blockNumberLength), 'hex'),
Buffer.from("ff".repeat(txNumberLength), 'hex'),
Buffer.from("ff".repeat(txOutputNumberLength), 'hex')])
const sliceStart = utxoIncludingAddressPrefix.length + address.length;
levelDB.createReadStream({gte: start,
lte: stop,
reversed:true})
.on('data', function (data) {
const out = TransactionOutput.prototype.initFromBinaryBlob(data.value);
toReturn = {};
toReturn["blockNumber"] = ethUtil.bufferToInt(data.key.slice(sliceStart, sliceStart + blockNumberLength))
toReturn["txNumberInBlock"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength, sliceStart + blockNumberLength + txNumberLength))
toReturn["outputNumberInTransaction"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength + txNumberLength, sliceStart + blockNumberLength + txNumberLength + txOutputNumberLength))
toReturn["to"] = ethUtil.toChecksumAddress(ethUtil.bufferToHex(out.to))
toReturn["value"] = out.value.toString(10);
// toReturn['output'] = out;
utxos.push(toReturn);
})
.on('error', function (err) {
console.log('Oh my!', err)
cb(err, null)
})
.on('close', function () {
// cb(null, utxos)
// console.log('Stream closed')
})
.on('end', function () {
cb(null, utxos)
// console.log('Stream ended')
})
}
}
================================================
FILE: app/helpers/getAllWithdrawsForAddress.js
================================================
const config = require("../config/config");
const ethUtil = require('ethereumjs-util');
const {blockPrefix,
utxoIncludingAddressPrefix,
utxoPrefix,
makeAddressIndex,
withdrawsForAddressPrefix} = config;
const {TransactionOutput} = require("../../lib/Tx/output");
const {TransactionInput} = require("../../lib/Tx/input");
const {BlockHeader} = require("../../lib/Block/blockHeader");
const assert = require('assert');
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
module.exports = function(levelDB) {
return getWithdrawsForAddress
// if (config.makeTransactionIndexForAddress) {
// return getWithdrawsAddressWithIndex;
// } else {
// return getTXsforAddress;
// }
async function getTXsforAddress(addressString, cb) {
addressString = ethUtil.addHexPrefix(addressString)
if (!ethUtil.isValidAddress(addressString)){
cb(true, null);
}
address = ethUtil.toBuffer(addressString);
const utxos = [];
const start = Buffer.concat([utxoPrefix,
Buffer.alloc(blockNumberLength),
Buffer.alloc(txNumberLength),
Buffer.alloc(txOutputNumberLength)])
const stop = Buffer.concat([utxoPrefix,
Buffer.from("ff".repeat(blockNumberLength), 'hex'),
Buffer.from("ff".repeat(txNumberLength), 'hex'),
Buffer.from("ff".repeat(txOutputNumberLength), 'hex')])
const sliceStart = utxoPrefix.length;
levelDB.createReadStream({gte: start,
lte: stop,
reversed:true})
.on('data', function (data) {
if (data.value.slice(0,txToAddressLength).equals(address)) {
const out = TransactionOutput.prototype.initFromBinaryBlob(data.value);
toReturn = {};
toReturn["blockNumber"] = ethUtil.bufferToInt(data.key.slice(sliceStart, sliceStart + blockNumberLength))
toReturn["txNumberInBlock"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength, sliceStart + blockNumberLength + txNumberLength))
toReturn["outputNumberInTransaction"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength + txNumberLength, sliceStart + blockNumberLength + txNumberLength + txOutputNumberLength))
toReturn["value"] = out.value.toString(10);
toReturn['output'] = out;
utxos.push(toReturn);
}
})
.on('error', function (err) {
console.log('Oh my!', err)
cb(err, null)
})
.on('close', function () {
// cb(null, utxos)
// console.log('Stream closed')
})
.on('end', function () {
cb(null, utxos)
// console.log('Stream ended')
})
}
async function getWithdrawsForAddress(addressString, cb) {
addressString = ethUtil.addHexPrefix(addressString)
if (!ethUtil.isValidAddress(addressString)){
cb(true, null);
}
address = ethUtil.toBuffer(addressString);
const utxos = [];
const start = Buffer.concat([withdrawsForAddressPrefix,
address,
Buffer.alloc(blockNumberLength),
Buffer.alloc(txNumberLength)])
const stop = Buffer.concat([withdrawsForAddressPrefix,
address,
Buffer.from("ff".repeat(blockNumberLength), 'hex'),
Buffer.from("ff".repeat(txNumberLength), 'hex'),])
const sliceStart = withdrawsForAddressPrefix.length + address.length;
levelDB.createReadStream({gte: start,
lte: stop,
reversed:true})
.on('data', function (data) {
toReturn = {};
toReturn["blockNumber"] = ethUtil.bufferToInt(data.key.slice(sliceStart, sliceStart + blockNumberLength))
toReturn["txNumberInBlock"] = ethUtil.bufferToInt(data.key.slice(sliceStart + blockNumberLength, sliceStart + blockNumberLength + txNumberLength))
utxos.push(toReturn);
})
.on('error', function (err) {
console.log('Oh my!', err)
cb(err, null)
})
.on('close', function () {
// cb(null, utxos)
// console.log('Stream closed')
})
.on('end', function () {
cb(null, utxos)
// console.log('Stream ended')
})
}
}
================================================
FILE: app/helpers/getBlock.js
================================================
const config = require("../config/config");
const blockPrefix = config.blockPrefix;
const Block = require("../../lib/Block/block");
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
module.exports = function(levelDB) {
return async function getBlockByNumber(blockNumber) {
const blockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(blockNumber)),blockNumberLength)
const key = Buffer.concat([blockPrefix, blockNumberBuffer]);
const blockBin = await levelDB.get(key);
const block = new Block(blockBin);
return block;
}
}
================================================
FILE: app/helpers/getTX.js
================================================
const config = require("../config/config");
const blockPrefix = config.blockPrefix;
const utxoPrefix = config.utxoPrefix;
const transactionPrefix = config.transactionPrefix;
const {TransactionOutput} = require("../../lib/Tx/output");
const {TransactionInput} = require("../../lib/Tx/input");
const {PlasmaTransaction, TxLengthForType} = require("../../lib/Tx/tx");
const {BlockHeader} = require("../../lib/Block/blockHeader");
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
module.exports = function(levelDB) {
return async function getTX(blockNumber, txNumber) {
const blockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(blockNumber)),blockNumberLength)
const txNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(txNumber)),txNumberLength)
const query = Buffer.concat([transactionPrefix,
blockNumberBuffer,
txNumberBuffer])
try {
const data = await levelDB.get(query);
const txType = ethUtil.bufferToInt(data.slice(txNumberLength,txNumberLength+txOutputNumberLength))
const txBin = data.slice(0, TxLengthForType[txType]);
const TX = PlasmaTransaction.prototype.initTxForTypeFromBinary(txType, txBin);
return TX
}
catch(err) {
return null;
}
}
}
================================================
FILE: app/helpers/getUTXO.js
================================================
const config = require("../config/config");
const blockPrefix = config.blockPrefix;
const utxoPrefix = config.utxoPrefix;
const {TransactionOutput} = require("../../lib/Tx/output");
const {TransactionInput} = require("../../lib/Tx/input");
const {BlockHeader} = require("../../lib/Block/blockHeader");
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
module.exports = function(levelDB) {
return async function getUTXO(blockNumber, txNumber, outputNumber) {
const blockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(blockNumber)),blockNumberLength)
const txNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(txNumber)),txNumberLength)
const txOutputNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(outputNumber)),txOutputNumberLength)
const query = Buffer.concat([utxoPrefix,
blockNumberBuffer,
txNumberBuffer,
txOutputNumberBuffer])
try {
const data = await levelDB.get(query);
return TransactionOutput.prototype.initFromBinaryBlob(data);
}
catch(err) {
return null;
}
}
}
================================================
FILE: app/helpers/hexDataToEncodedBytes.js
================================================
const ethUtil = require('ethereumjs-util');
module.exports = function convertForRemix(hexString){
const hex = ethUtil.addHexPrefix(hexString);
const bArray = ethUtil.toBuffer(hex);
const encodedArray = [];
for (var i=0; i<bArray.length; i++) {
const b = bArray.slice(i, i+1);
const h = ethUtil.addHexPrefix(ethUtil.bufferToHex(b));
encodedArray.push(h);
}
return encodedArray;
}
================================================
FILE: app/helpers/prepareProofForTX.js
================================================
//demo purposes only
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require('../../lib/Tx/tx');
const encodeForRemix = require("./hexDataToEncodedBytes");
module.exports = function(levelDB) {
const getBlockByNumber = require('./getBlock')(levelDB);
const getTX = require('./getTX')(levelDB);
return async function prepareProofsForTX(blockNumber, txNumberInBlock) {
const block = await getBlockByNumber(blockNumber);
const tx = block.transactions[txNumberInBlock];
let proof = ethUtil.bufferToHex(Buffer.concat(block.merkleTree.getProof(txNumberInBlock, true)));
const raw = tx.raw.filter((r) => {
return r != undefined;
})
const encodedTx = ethUtil.bufferToHex(Buffer.concat(raw))
// if (proof == "0x") {
// proof = "0x00"
// }
// console.log(block.header.merkleRootHash.toString('hex'));
// console.log(JSON.stringify(encodeForRemix(proof)));
// console.log(JSON.stringify(encodeForRemix(encodedTx)));
return {
blockNumber: blockNumber,
txNumberInBlock: txNumberInBlock,
merkleProof: proof,
tx: encodedTx
}
}
}
================================================
FILE: app/helpers/processDepositEvent.js
================================================
const createFundingTransaction = require('./createFundingTransaction');
const Web3 = require('web3');
const BN = Web3.utils.BN;
const config = require('../config/config');
const ethUtil = require('ethereumjs-util');
module.exports = function(app, levelDB, web3) {
return async function processDepositEvent(event){
const {_from, _amount, _depositIndex} = event.returnValues;
let depositIndexBN = new BN(_depositIndex);
const depositIndexKey = Buffer.concat([config.depositIndexPrefix, ethUtil.toBuffer(depositIndexBN)]);
try{
const existingKey = await levelDB.get(depositIndexKey);
return
}
catch(error) {
if (error.type !== "NotFoundError"){
throw error
}
await levelDB.put(depositIndexKey, Buffer.alloc(1, "0x01", "hex"))
}
const tx = createFundingTransaction(_from, new Web3.utils.BN(_amount), depositIndexBN);
let txRaw = ethUtil.bufferToHex(Buffer.concat(tx.clearRaw(false, false)));
const txHash = tx.hash(false,false).toString('hex');
const signature = await web3.eth.sign(txRaw, config.plasmaOperatorAddress);
tx.serializeSignature(signature);
// const pubKey = ethUtil.ecrecover(txHash, ethUtil.bufferToInt(tx.v), tx.r, tx.s);
// const signedFromAddress = ethUtil.publicToAddress(pubKey).toString('hex');
if (tx.validate()) {
app.txQueueArray.push(tx);
// console.log("Pushed new TX")
console.log(event);
}
else {
// console.log("Invalid funding TX");
}
}
}
================================================
FILE: app/helpers/processExpressWithdrawMadeEvent.js
================================================
const createFundingTransaction = require('./createFundingTransaction');
const Web3 = require('web3');
const BN = Web3.utils.BN;
const config = require('../config/config');
const ethUtil = require('ethereumjs-util');
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../../lib/dataStructureLengths');
module.exports = function(app, levelDB, web3) {
return async function processExpressWithdrawMakeEvent(event){
const {_withdrawTxBlockNumber, _withdrawTxNumberInBlock, _from} = event.returnValues;
let txBlockNumberBN = new BN(_withdrawTxBlockNumber);
let txNumberInBlockBN = new BN(_withdrawTxNumberInBlock);
let senderAddress = ethUtil.toBuffer(_from);
const keyForWithdrawalIndex = Buffer.concat([config.withdrawsForAddressPrefix,
senderAddress,
ethUtil.setLengthLeft(ethUtil.toBuffer(txBlockNumberBN),blockNumberLength),
ethUtil.setLengthLeft(ethUtil.toBuffer(txNumberInBlockBN),txNumberLength)])
await levelDB.del(keyForWithdrawalIndex);
}
}
================================================
FILE: app/helpers/processWithdrawFinalazedEvent.js
================================================
const createFundingTransaction = require('./createFundingTransaction');
const Web3 = require('web3');
const BN = Web3.utils.BN;
const config = require('../config/config');
const ethUtil = require('ethereumjs-util');
module.exports = function(app, web3) {
return async function processWithdrawFinalizedEvent(event){
const {_blockNumber, _txNumberInBlock, _outputNumberInTX} = event.returnValues;
const keyForWithdrawalIndex = Buffer.concat([config.withdrawsForAddressPrefix,
senderAddress,
ethutil.blockNumber,
tx.transactionNumberInBlock])
let depositIndexBN = new BN(_depositIndex);
const tx = createFundingTransaction(_from, new Web3.utils.BN(_amount), depositIndexBN);
let txRaw = ethUtil.bufferToHex(Buffer.concat(tx.clearRaw(false, false)));
const txHash = tx.hash(false,false).toString('hex');
const signature = await web3.eth.sign(txRaw, config.plasmaOperatorAddress);
tx.serializeSignature(signature);
// const pubKey = ethUtil.ecrecover(txHash, ethUtil.bufferToInt(tx.v), tx.r, tx.s);
// const signedFromAddress = ethUtil.publicToAddress(pubKey).toString('hex');
if (tx.validate()) {
app.txQueueArray.push(tx);
console.log("Pushed new TX")
console.log(event);
}
else {
console.log("Invalid funding TX");
}
}
}
================================================
FILE: app/helpers/processWithdrawStartedEvent.js
================================================
================================================
FILE: app/helpers/signatureChecks.js
================================================
const ethUtil = require('ethereumjs-util');
function signForECRECOVER(dataBuffer, privKeyBuffer) {
const hash = ethUtil.hashPersonalMessage(dataBuffer);
const signatureObject = ethUtil.ecsign(hash, privKeyBuffer);
return {hash, signatureObject};
}
function checkSender(dataBuffer, signatureObject, address) {
const hash = ethUtil.hashPersonalMessage(dataBuffer);
const pubKey = ethUtil.ecrecover(hash, signatureObject.v, signatureObject.r, signatureObject.s);
const signedFromAddress = ethUtil.publicToAddress(pubKey).toString('hex');
return signedFromAddress == address;
}
function testSignature() {
const data = "test";
const dataBuffer = Buffer.from(data);
const {hash, signatureObject} = signForECRECOVER(dataBuffer, privKeys[0]);
const valid = checkSender(dataBuffer, signatureObject, ethUtil.privateToAddress(privKeys[0]).toString('hex'));
assert(valid);
}
module.exports = {
signForECRECOVER,
checkSender,
}
================================================
FILE: app/miner.js
================================================
const config = require("./config/config");
const blockPrefix = config.blockPrefix;
const utxoPrefix = config.utxoPrefix;
const utxoIncludingAddressPrefix = config.utxoIncludingAddressPrefix;
const makeAddressIndex = config.makeAddressIndex;
const headerPrefix = config.headerPrefix;
const transactionPrefix=config.transactionPrefix;
const blockTime = config.blockTime;
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength,
txOutputNumberLength,
txAmountLength,
txToAddressLength} = require('../lib/dataStructureLengths');
const Web3 = require('web3');
const Block = require('../lib/Block/block');
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN;
const plasmaOperatorAddress = config.plasmaOperatorAddress;
const assert = require('assert');
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType,
NumInputsForType,
NumOutputsForType} = require('../lib/Tx/tx');
const encodeForRemix = require('./helpers/hexDataToEncodedBytes');
module.exports = function(app, levelDB, web3) {
const checkSpendingTX = require('./helpers/checkSpendingTX')(levelDB);
const getBlockByNumber = require('./helpers/getBlock')(levelDB);
function startBlockProcessing(app, fromBlock) {
processBlockForDepositEvents(fromBlock)().then((_dispose) => {
console.log("Started block processing loop");
})
function processBlockForDepositEvents(previousBlockNumber) {
return async function() {
try{
let lastblock = await web3.eth.getBlockNumber();
if (previousBlockNumber == -1) {
previousBlockNumber = lastblock-1;
}
if (lastblock > previousBlockNumber) {
lastblock = previousBlockNumber + 1;
// lastProcessedBlock = lastblock;
console.log("Started at block " + lastblock);
await processBlock(lastblock)();
setTimeout(processBlockForDepositEvents(lastblock), 100);
return;
} else {
setTimeout(processBlockForDepositEvents(lastblock), 1000);
return;
}
}
catch(error) {
console.log("Error processing block for events : " + error);
if (error.name == "Submitting too far in future") {
setImmediate(processBlockForDepositEvents(error.message));
return;
}
setImmediate(processBlockForDepositEvents(previousBlockNumber));
}
}
}
function processBlock(blockNumber) {
return async function() {
const expressWithdrawEventsInBlock = await app.DeployedPlasmaContract.getPastEvents("ExpressWithdrawMadeEvent",{
fromBlock: blockNumber,
toBlock: blockNumber
});
if (expressWithdrawEventsInBlock.length > 0) {
for (let i = 0; i< expressWithdrawEventsInBlock.length; i++){
await app.processExpressWithdrawMakeEvent(expressWithdrawEventsInBlock[i]);
}
}
const depositEventsInBlock = await app.DeployedPlasmaContract.getPastEvents("DepositEvent",{
fromBlock: blockNumber,
toBlock: blockNumber
});
if (depositEventsInBlock.length > 0) {
for (let i = 0; i< depositEventsInBlock.length; i++){
await app.processDepositEvent(depositEventsInBlock[i]);
}
}
const blockNumberBN = Web3.utils.toBN(blockNumber);
const newBlockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(blockNumberBN), blockNumberLength);
await levelDB.put(config.lastEventProcessedBlockPrefix, newBlockNumberBuffer);
}
}
}
async function createBlock() {
try{
try{
lastBlock = await levelDB.get('lastBlockNumber');
}
catch(error) {
lastBlock = ethUtil.setLengthLeft(ethUtil.toBuffer(new BN(0)),blockNumberLength)
await levelDB.put('lastBlockNumber', lastBlock);
}
try{
lastBlockHash = await levelDB.get('lastBlockHash');
}
catch(error) {
lastBlockHash = ethUtil.sha3('bankex.com')
await levelDB.put('lastBlockHash', lastBlockHash);
}
let sliceLen;
let TXs;
console.log("Mining a block")
console.log("Queue length = " + app.txQueueArray.length);
if (app.txQueueArray.length == 0) {
return false;
}
if (app.txQueueArray.length > 2**16){
// TXs = app.txQueueArray.splice(0, 2**16);
TXs = app.txQueueArray.slice(0, 2**16);
sliceLen = 2**16
} else {
// TXs = app.txQueueArray.splice(0, app.txQueueArray.length);
TXs = app.txQueueArray.slice(0, app.txQueueArray.length);
sliceLen = app.txQueueArray.length
}
if (TXs.length == 0) {
return false;
}
const allReferencedInputs = {};
TXs = TXs.filter((tx) => {
const key = tx.getKey();
if (key === "000000000000000000") {
return true;
}
if (!allReferencedInputs[key]) {
allReferencedInputs.key = true;
return true;
}
return false;
})
let TXsWithValidInputs = [];
for (let i=0; i<TXs.length; i++) {
const valid = await checkSpendingTX(TXs[i]);
if (valid) {
TXsWithValidInputs.push(TXs[i]);
}
}
TXs = TXsWithValidInputs
for (let i=0; i<TXs.length; i++) {
TXs[i].assignNumber(i);
}
if (TXs.length == 0) {
return true;
}
const lastBlockNumber = Web3.utils.toBN(ethUtil.addHexPrefix(lastBlock.toString('hex')));
const newBlockNumber = lastBlockNumber.add(new BN(1));
const newBlockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(newBlockNumber), blockNumberLength);
const blockParams = {
blockNumber: newBlockNumberBuffer,
parentHash: lastBlockHash,
transactions: TXs
}
const block = new Block(blockParams);
let blockRaw = block.clearRaw(false);
blockRaw = ethUtil.bufferToHex(Buffer.concat(blockRaw));
const signature = await web3.eth.sign(blockRaw, config.plasmaOperatorAddress);
const blockHash = block.hash(false).toString('hex');
console.log("Block hash = " + blockHash);
console.log("Block signature = " + signature);
block.serializeSignature(signature);
assert(block.validate());
await writeBlock(block);
console.log("Created block " + newBlockNumber);
app.txQueueArray = app.txQueueArray.slice(sliceLen);
// const submitted = await submitBlockHeader(block)();
// if (!submitted) {
// throw("Couldn't submit");
// }
// console.log("Submitted header for block "+newBlockNumber)
return true;
}
catch(err){
console.log(err);
}
}
async function writeBlock(block) {
var writeRequest = levelDB.batch()
.put('lastBlockNumber', block.header.blockNumber)
.put('lastBlockHash', block.hash(true))
.put(Buffer.concat([blockPrefix,block.header.blockNumber]),Buffer.concat(block.raw))
.put(Buffer.concat([headerPrefix,block.header.blockNumber]),Buffer.concat(block.header.raw))
block.transactions.forEach((tx, i)=>{
const senderAddress = tx.getSenderAddress();
for (let inpIndex of [0,1]) {
const input = tx.getTransactionInput(inpIndex)
if (input && typeof input != "undefined") {
const keyForUTXO = Buffer.concat([utxoPrefix, input.blockNumber, input.txNumberInBlock, input.outputNumberInTransaction]);
writeRequest.del(keyForUTXO)
if (makeAddressIndex) {
const keyForAddressUTXO = Buffer.concat([utxoIncludingAddressPrefix, senderAddress, input.blockNumber, input.txNumberInBlock, input.outputNumberInTransaction]);
writeRequest.del(keyForAddressUTXO)
}
}
}
for (let outIndex of [0,1]) {
const output = tx.getTransactionOutput(outIndex);
if (output && typeof output != "undefined" && !(output.outputNumberInTransaction.equals(Buffer.from('ff', 'hex'))) ) {
const keyForUTXO = Buffer.concat([utxoPrefix, block.header.blockNumber, tx.transactionNumberInBlock, output.outputNumberInTransaction]);
writeRequest.put(keyForUTXO, Buffer.concat(output.raw))
if (makeAddressIndex) {
const recipientAddress = output.to;
const keyForAddressUTXO = Buffer.concat([utxoIncludingAddressPrefix, recipientAddress, block.header.blockNumber, tx.transactionNumberInBlock, output.outputNumberInTransaction]);
writeRequest.put(keyForAddressUTXO, Buffer.concat(output.raw))
}
}
}
if (tx.transactionTypeUInt() == TxTypeWithdraw) {
const keyForWithdrawalIndex = Buffer.concat([config.withdrawsForAddressPrefix,senderAddress,block.header.blockNumber, tx.transactionNumberInBlock])
writeRequest.put(keyForWithdrawalIndex, Buffer.alloc(1, "0x01", "hex"))
}
if (config.makeTransactionIndexForAddress) {
// if (ethUtil.bufferToHex(senderAddress).toLowerCase() != config.plasmaOperatorAddress.toLowerCase()) {
const transactionIndexKey = Buffer.concat([config.txForAddressIndexPrefix, senderAddress, block.header.blockNumber, tx.transactionNumberInBlock])
writeRequest.put(transactionIndexKey, Buffer.alloc(1, "0x01", "hex"))
// }
}
const r = tx.clearRaw(true, true);
writeRequest.put(Buffer.concat([transactionPrefix, block.header.blockNumber, tx.transactionNumberInBlock]), Buffer.concat(r))
})
await writeRequest.write();
}
function startHeadersSubmission(app, fromBlock) {
processBlockNumberForSubmission(fromBlock)().then((_dispose) => {
console.log("Started header submission loop");
})
function processBlockNumberForSubmission(previousBlockNumber) {
return async function() {
try{
let lastblock = await levelDB.get("lastBlockNumber");
lastblock = ethUtil.bufferToInt(lastblock);
if (lastblock == 0) {
setTimeout(processBlockNumberForSubmission(lastblock), 1000);
return;
}
if (previousBlockNumber == -1) {
previousBlockNumber = lastblock-1;
}
if (lastblock > previousBlockNumber) {
lastblock = previousBlockNumber + 1;
console.log("Started header submission " + lastblock);
await processBlockForSubmission(lastblock)();
setTimeout(processBlockNumberForSubmission(lastblock), 100);
return;
} else {
setTimeout(processBlockNumberForSubmission(lastblock), 1000);
return;
}
}
catch(error) {
console.log("Error processing block header : " + error);
setImmediate(processBlockNumberForSubmission(previousBlockNumber));
}
}
}
function processBlockForSubmission(blockNumber) {
return async function() {
const block = await getBlockByNumber(blockNumber)
const submitted = await submitBlockHeader(block)();
if (!submitted) {
throw("Couldn't submit");
}
console.log("Submitted header for block "+blockNumber)
const blockNumberBN = Web3.utils.toBN(blockNumber);
const newBlockNumberBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(blockNumberBN), blockNumberLength);
await levelDB.put(config.lastSubmittedHeaderPrefix, newBlockNumberBuffer);
}
}
}
function submitBlockHeader(block){
return async function() {
const hexData = ethUtil.addHexPrefix(Buffer.concat(block.header.raw).toString('hex'));
const blockNumber = Web3.utils.toBN(ethUtil.bufferToInt(block.header.blockNumber));
console.log("Submitting header = " + hexData);
let lastSubmitted;
// console.log(JSON.stringify(encodeForRemix(hexData)));
try{
lastSubmitted = await app.DeployedPlasmaContract.methods.lastBlockNumber().call();
lastSubmitted = Web3.utils.toBN(lastSubmitted)
if (lastSubmitted.gte(blockNumber)) {
return true;
}
const distance = blockNumber.sub(lastSubmitted);
const ONE = Web3.utils.toBN(1);
if (distance.gt(ONE)) {
throw {name: "Submitting too far in future", message: lastSubmitted.toNumber()}
}
const gasCost = await app.DeployedPlasmaContract.methods.submitBlockHeader(hexData).estimateGas({from: config.plasmaOperatorAddress, gas: 1e6})
const res = await app.DeployedPlasmaContract.methods.submitBlockHeader(hexData).send({from: config.plasmaOperatorAddress, gas: gasCost});
const submissionEvent = res.events.HeaderSubmittedEvent.returnValues;
console.log(submissionEvent);
assert(submissionEvent._blockNumber == blockNumber.toString(10));
return true;
}
catch(err){
console.log(err)
if (err.name == "Submitting too far in future"){
throw err;
}
if (err.message == "Returned error: gas required exceeds allowance or always failing transaction") {
}
}
return false
}
}
async function tryToMine() {
const mined = await createBlock();
setTimeout(tryToMine, blockTime)
return true
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
tryToMine().then((res) => {
console.log("Started mining loop")
})
levelDB.get(config.lastEventProcessedBlockPrefix)
.then((res) => {
const lastProcessedBlock = Web3.utils.toBN(ethUtil.addHexPrefix(res.toString('hex'))).toNumber();
// const lastProcessedBlock = 1408111 - 1;
startBlockProcessing(app, lastProcessedBlock);
})
.catch((e) => {
const lastProcessedBlock = -1;
// const lastProcessedBlock = 1345677;
startBlockProcessing(app, lastProcessedBlock);
});
levelDB.get(config.lastSubmittedHeaderPrefix)
.then((res) => {
const lastProcessedHeader = Web3.utils.toBN(ethUtil.addHexPrefix(res.toString('hex'))).toNumber();
// const lastProcessedBlock = 1372150;
startHeadersSubmission(app, lastProcessedHeader);
})
.catch((e) => {
const lastProcessedHeader = -1;
// const lastProcessedBlock = 1345677;
startHeadersSubmission(app, lastProcessedHeader);
});
}
================================================
FILE: compile.js
================================================
const assert = require('assert');
var fs = require("fs");
var solc = require('solc');
var rimraf = require('rimraf');
rimraf.sync('./build/contracts');
const Artifactor = require("truffle-artifactor");
const TruffleContract = require('truffle-contract');
const Web3 = require("web3");
const util = require('util');
const artifactor = new Artifactor("./build/contracts");
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const Contracts = {
"PlasmaParent" : ["PlasmaParent"]
}
function findImports (path) {
try{
var input = fs.readFileSync("./contracts/"+path);
var ret = {contents: input.toString()};
// ret[path] = input.toString();
return ret;
}
catch(error){
return { error: 'File not found' }
}
}
async function main() {
console.log("Compiling contracts...");
let inputs = {};
let output;
for (let contract in Contracts){
inputs[contract+'.sol'] = fs.readFileSync("./contracts/" + contract + '.sol', 'utf8');
}
try{
output = solc.compile({ sources: inputs }, 1, findImports)
}
catch(error){
console.log(error);
}
for (err in output.errors){
console.log(output.errors[err]);
}
if (!output.contracts || Object.keys(output.contracts).length == 0){
throw("Didn't compile");
}
const libraryRE = new RegExp('__([a-zA-Z.:]+)[_]*', 'g');
for (let property in output.contracts) {
let inList = false;
for (let c in Contracts) {
for (let cName of Contracts[c]){
let fullName = c+".sol:"+cName;
if (property === fullName){
inList = true;
break;
}
}
if (inList){
break;
}
}
if (!inList){
continue;
}
if (output.contracts.hasOwnProperty(property)) {
let cont = output.contracts[property];
let bytecode = cont.bytecode;
if (libraryRE.test(bytecode)){
console.log("Linking is necessary!");
var m;
m = libraryRE.exec(bytecode);
while ((m = libraryRE.exec(bytecode)) !== null) {
if (m.index === libraryRE.lastIndex) {
libraryRE.lastIndex++;
}
const libName = m[1];
const libObject = {};
libObject[libName] = output.contracts[libName].bytecode;
}
}
if (bytecode == ""){
console.log("Bytecode is empty!");
}
try{
var abi = JSON.parse(cont.interface);
}
catch(e){
console.log(e);
}
const p = property.split(':');
await artifactor.save({contract_name: p[p.length-1], abi: abi, unlinked_binary: bytecode});
}
}
console.log("Done compiling");
}
module.exports = main;
================================================
FILE: contracts/PlasmaParent.sol
================================================
pragma solidity ^0.4.18;
library ByteSlice {
struct Slice {
uint _unsafe_memPtr; // Memory address of the first byte.
uint _unsafe_length; // Length.
}
/// @dev Converts bytes to a slice.
/// @param self The bytes.
/// @return A slice.
function slice(bytes memory self) internal pure returns (Slice memory newSlice) {
assembly {
let length := mload(self)
let memPtr := add(self, 0x20)
mstore(newSlice, mul(memPtr, iszero(iszero(length))))
mstore(add(newSlice, 0x20), length)
}
}
/// @dev Converts bytes to a slice from the given starting position.
/// 'startpos' <= 'len(slice)'
/// @param self The bytes.
/// @param startpos The starting position.
/// @return A slice.
function slice(bytes memory self, uint startpos) internal pure returns (Slice memory) {
return slice(slice(self), startpos);
}
/// @dev Converts bytes to a slice from the given starting position.
/// -len(slice) <= 'startpos' <= 'len(slice)'
/// @param self The bytes.
/// @param startpos The starting position.
/// @return A slice.
function slice(bytes memory self, int startpos) internal pure returns (Slice memory) {
return slice(slice(self), startpos);
}
/// @dev Converts bytes to a slice from the given starting-position, and end-position.
/// 'startpos <= len(slice) and startpos <= endpos'
/// 'endpos <= len(slice)'
/// @param self The bytes.
/// @param startpos The starting position.
/// @param endpos The end position.
/// @return A slice.
function slice(bytes memory self, uint startpos, uint endpos) internal view returns (Slice memory) {
return slice(slice(self), startpos, endpos);
}
/// @dev Converts bytes to a slice from the given starting-position, and end-position.
/// Warning: higher cost then using unsigned integers.
/// @param self The bytes.
/// @param startpos The starting position.
/// @param endpos The end position.
/// @return A slice.
function slice(bytes memory self, int startpos, int endpos) internal view returns (Slice memory) {
return slice(slice(self), startpos, endpos);
}
/// @dev Get the length of the slice (in bytes).
/// @param self The slice.
/// @return the length.
function len(Slice memory self) internal pure returns (uint) {
return self._unsafe_length;
}
/// @dev Returns the byte from the backing array at a given index.
/// The function will throw unless 'index < len(slice)'
/// @param self The slice.
/// @param index The index.
/// @return The byte at that index.
function at(Slice memory self, uint index) internal pure returns (byte b) {
if (index >= self._unsafe_length)
revert();
uint bb;
assembly {
// Get byte at index, and format to 'byte' variable.
bb := byte(0, mload(add(mload(self), index)))
}
b = byte(bb);
}
/// @dev Returns the byte from the backing array at a given index.
/// The function will throw unless '-len(self) <= index < len(self)'.
/// @param self The slice.
/// @param index The index.
/// @return The byte at that index.
function at(Slice memory self, int index) internal pure returns (byte b) {
if (index >= 0)
return at(self, uint(index));
uint iAbs = uint(-index);
if (iAbs > self._unsafe_length)
revert();
return at(self, self._unsafe_length - iAbs);
}
/// @dev Set the byte at the given index.
/// The function will throw unless 'index < len(slice)'
/// @param self The slice.
/// @param index The index.
/// @return The byte at that index.
function set(Slice memory self, uint index, byte b) internal pure {
if (index >= self._unsafe_length)
revert();
assembly {
mstore8(add(mload(self), index), byte(0, b))
}
}
/// @dev Set the byte at the given index.
/// The function will throw unless '-len(self) <= index < len(self)'.
/// @param self The slice.
/// @param index The index.
/// @return The byte at that index.
function set(Slice memory self, int index, byte b) internal pure {
if (index >= 0)
return set(self, uint(index), b);
uint iAbs = uint(-index);
if (iAbs > self._unsafe_length)
revert();
return set(self, self._unsafe_length - iAbs, b);
}
/// @dev Creates a copy of the slice.
/// @param self The slice.
/// @return the new reference.
function slice(Slice memory self) internal pure returns (Slice memory newSlice) {
newSlice._unsafe_memPtr = self._unsafe_memPtr;
newSlice._unsafe_length = self._unsafe_length;
}
/// @dev Create a new slice from the given starting position.
/// 'startpos' <= 'len(slice)'
/// @param self The slice.
/// @param startpos The starting position.
/// @return The new slice.
function slice(Slice memory self, uint startpos) internal pure returns (Slice memory newSlice) {
uint length = self._unsafe_length;
if (startpos > length)
revert();
assembly {
length := sub(length, startpos)
let newMemPtr := mul(add(mload(self), startpos), iszero(iszero(length)))
mstore(newSlice, newMemPtr)
mstore(add(newSlice, 0x20), length)
}
}
/// @dev Create a new slice from the given starting position.
/// -len(slice) <= 'startpos' <= 'len(slice)'
/// @param self The slice.
/// @param startpos The starting position.
/// @return The new slice.
function slice(Slice memory self, int startpos) internal pure returns (Slice memory newSlice) {
uint startpos_;
uint length = self._unsafe_length;
if (startpos >= 0) {
startpos_ = uint(startpos);
if (startpos_ > length)
revert();
} else {
startpos_ = uint(-startpos);
if (startpos_ > length)
revert();
startpos_ = length - startpos_;
}
assembly {
length := sub(length, startpos_)
let newMemPtr := mul(add(mload(self), startpos_), iszero(iszero(length)))
mstore(newSlice, newMemPtr)
mstore(add(newSlice, 0x20), length)
}
}
/// @dev Create a new slice from a given slice, starting-position, and end-position.
/// 'startpos <= len(slice) and startpos <= endpos'
/// 'endpos <= len(slice)'
/// @param self The slice.
/// @param startpos The starting position.
/// @param endpos The end position.
/// @return the new slice.
function slice(Slice memory self, uint startpos, uint endpos) internal pure returns (Slice memory newSlice) {
uint length = self._unsafe_length;
if (startpos > length || endpos > length || startpos > endpos)
revert();
assembly {
length := sub(endpos, startpos)
let newMemPtr := mul(add(mload(self), startpos), iszero(iszero(length)))
mstore(newSlice, newMemPtr)
mstore(add(newSlice, 0x20), length)
}
}
/// Same as new(Slice memory, uint, uint) but allows for negative indices.
/// Warning: higher cost then using unsigned integers.
/// @param self The slice.
/// @param startpos The starting position.
/// @param endpos The end position.
/// @return The new slice.
function slice(Slice memory self, int startpos, int endpos) internal pure returns (Slice memory newSlice) {
// Don't allow slice on bytes of length 0.
uint startpos_;
uint endpos_;
uint length = self._unsafe_length;
if (startpos < 0) {
startpos_ = uint(-startpos);
if (startpos_ > length)
revert();
startpos_ = length - startpos_;
}
else {
startpos_ = uint(startpos);
if (startpos_ > length)
revert();
}
if (endpos < 0) {
endpos_ = uint(-endpos);
if (endpos_ > length)
revert();
endpos_ = length - endpos_;
}
else {
endpos_ = uint(endpos);
if (endpos_ > length)
revert();
}
if(startpos_ > endpos_)
revert();
assembly {
length := sub(endpos_, startpos_)
let newMemPtr := mul(add(mload(self), startpos_), iszero(iszero(length)))
mstore(newSlice, newMemPtr)
mstore(add(newSlice, 0x20), length)
}
}
/// @dev Creates a 'bytes memory' variable from a slice, copying the data.
/// Bytes are copied from the memory address 'self._unsafe_memPtr'.
/// The number of bytes copied is 'self._unsafe_length'.
/// @param self The slice.
/// @return The bytes variable.
function toBytes(Slice memory self) internal constant returns (bytes memory bts) {
uint length = self._unsafe_length;
if (length == 0)
return;
uint memPtr = self._unsafe_memPtr;
bts = new bytes(length);
// We can do word-by-word copying since 'bts' was the last thing to be
// allocated. Just overwrite any excess bytes at the end with zeroes.
assembly {
let i := 0
let btsOffset := add(bts, 0x20)
let words := div(add(length, 31), 32)
tag_loop:
jumpi(end, gt(i, words))
{
let offset := mul(i, 32)
mstore(add(btsOffset, offset), mload(add(memPtr, offset)))
i := add(i, 1)
}
jump(tag_loop)
end:
mstore(add(add(bts, length), 0x20), 0)
}
}
/// @dev Creates an ascii-encoded 'string' variable from a slice, copying the data.
/// Bytes are copied from the memory address 'self._unsafe_memPtr'.
/// The number of bytes copied is 'self._unsafe_length'.
/// @param self The slice.
/// @return The bytes variable.
function toAscii(Slice memory self) internal view returns (string memory str) {
return string(toBytes(self));
}
/// @dev Check if two slices are equal.
/// @param self The slice.
/// @param other The other slice.
/// @return True if both slices point to the same memory address, and has the same length.
function equals(Slice memory self, Slice memory other) internal pure returns (bool) {
return (
self._unsafe_length == other._unsafe_length &&
self._unsafe_memPtr == other._unsafe_memPtr
);
}
}
library Bytes {
function concat(bytes memory self, bytes memory bts) internal view returns (bytes memory newBts) {
uint totLen = self.length + bts.length;
if (totLen == 0)
return;
newBts = new bytes(totLen);
assembly {
let i := 0
let inOffset := 0
let outOffset := add(newBts, 0x20)
let words := 0
let tag := tag_bts
tag_self:
inOffset := add(self, 0x20)
words := div(add(mload(self), 31), 32)
jump(tag_loop)
tag_bts:
i := 0
inOffset := add(bts, 0x20)
outOffset := add(newBts, add(0x20, mload(self)))
words := div(add(mload(bts), 31), 32)
tag := tag_end
tag_loop:
jumpi(tag, gt(i, words))
{
let offset := mul(i, 32)
outOffset := add(outOffset, offset)
mstore(outOffset, mload(add(inOffset, offset)))
i := add(i, 1)
}
jump(tag_loop)
tag_end:
mstore(add(newBts, add(totLen, 0x20)), 0)
}
}
function uintToBytes(uint self) internal pure returns (bytes memory s) {
uint maxlength = 100;
bytes memory reversed = new bytes(maxlength);
uint i = 0;
while (self != 0) {
uint remainder = self % 10;
self = self / 10;
reversed[i++] = byte(48 + remainder);
}
s = new bytes(i);
for (uint j = 0; j < i; j++) {
s[j] = reversed[i - 1 - j];
}
return s;
}
}
contract PlasmaParent {
using Bytes for *;
using ByteSlice for *;
address public owner = msg.sender;
mapping(address => bool) public operators;
uint32 public blockHeaderLength = 137;
uint256 public lastBlockNumber = 0;
uint256 public lastEthBlockNumber = block.number;
uint256 public depositCounterInBlock = 0;
struct Header {
uint32 blockNumber;
uint32 numTransactions;
uint8 v;
bytes32 previousBlockHash;
bytes32 merkleRootHash;
bytes32 r;
bytes32 s;
}
struct TransactionInput {
uint32 blockNumber;
uint32 txNumberInBlock;
uint8 outputNumberInTX;
uint256 amount;
}
struct TransactionOutput {
address recipient;
uint8 outputNumberInTX;
uint256 amount;
}
struct PlasmaTransaction {
uint32 txNumberInBlock;
uint8 txType;
TransactionInput[] inputs;
TransactionOutput[] outputs;
uint8 v;
bytes32 r;
bytes32 s;
}
uint256 constant TxTypeNull = 0;
uint256 constant TxTypeSplit = 1;
uint256 constant TxTypeMerge = 2;
uint256 constant TxTypeWithdraw = 3;
uint256 constant TxTypeFund = 4;
uint256 constant TxTypeTransfer = 5;
uint256[6] NumInputsForType = [0, 1, 2, 1, 1, 1];
uint256[6] NumOutputsForType = [0, 2, 1, 1, 2, 1];
uint256 constant SignatureLength = 65;
uint256 constant BlockNumberLength = 4;
uint256 constant TxNumberLength = 4;
uint256 constant TxTypeLength = 1;
uint256 constant TxOutputNumberLength = 1;
uint256 constant PreviousHashLength = 32;
uint256 constant MerkleRootHashLength = 32;
uint256 constant TxAmountLength = 32;
bytes constant PersonalMessagePrefixBytes = "\x19Ethereum Signed Message:\n";
uint256 constant PreviousBlockPersonalHashLength = BlockNumberLength +
TxNumberLength +
PreviousHashLength +
MerkleRootHashLength +
SignatureLength;
uint256 constant NewBlockPersonalHashLength = BlockNumberLength +
TxNumberLength +
PreviousHashLength +
MerkleRootHashLength;
uint256 TransactionOutputLength = 20 + TxOutputNumberLength + TxAmountLength;
uint256 TransactionInputLength = BlockNumberLength + TxNumberLength + TxOutputNumberLength + TxAmountLength;
uint256 TxMainLength = TxNumberLength + TxTypeLength + SignatureLength;
uint256[6] TxLengthForType = [0,
TxMainLength + 1*TransactionInputLength + 2*TransactionOutputLength,
TxMainLength + 2*TransactionInputLength + 1*TransactionOutputLength,
TxMainLength + 1*TransactionInputLength + 1*TransactionOutputLength,
TxMainLength + 1*TransactionInputLength + 2*TransactionOutputLength,
TxMainLength + 1*TransactionInputLength + 1*TransactionOutputLength];
mapping (uint256 => Header) public headers;
event Debug(bool indexed _success, bytes32 indexed _b, address indexed _signer);
event DebugUint(uint256 indexed _1, uint256 indexed _2, uint256 indexed _3);
event SigEvent(address indexed _signer, bytes32 indexed _r, bytes32 indexed _s);
function extract32(bytes data, uint pos) pure internal returns (bytes32 result) {
for (uint256 i = 0; i < 32; i++) {
result ^= (bytes32(0xff00000000000000000000000000000000000000000000000000000000000000)&data[i+pos])>>(i*8);
}
}
function extract20(bytes data, uint pos) pure internal returns (bytes20 result) {
for (uint256 i = 0; i < 20; i++) {
result ^= (bytes20(0xff00000000000000000000000000000000000000)&data[i+pos])>>(i*8);
}
}
function extract4(bytes data, uint pos) pure internal returns (bytes4 result) {
for (uint256 i = 0; i < 4; i++) {
result ^= (bytes4(0xff000000)&data[i+pos])>>(i*8);
}
}
function extract2(bytes data, uint pos) pure internal returns (bytes2 result) {
for (uint256 i = 0; i < 2; i++) {
result ^= (bytes2(0xff00)&data[i+pos])>>(i*8);
}
}
function extract1(bytes data, uint pos) pure internal returns (bytes1 result) {
for (uint256 i = 0; i < 1; i++) {
result ^= (bytes1(0xff)&data[i+pos])>>(i*8);
}
}
function PlasmaParent() public {
operators[msg.sender] = true;
}
function setOperator(address _op, bool _status) public returns (bool success) {
require(msg.sender == owner);
operators[_op] = _status;
return true;
}
event HeaderSubmittedEvent(address indexed _signer, uint32 indexed _blockNumber, bytes32 indexed _blockHash);
function submitBlockHeader(bytes header) public returns (bool success) {
require(operators[msg.sender]);
require(header.length == blockHeaderLength);
uint32 blockNumber = uint32(extract4(header, 0));
uint32 numTransactions = uint32(extract4(header, BlockNumberLength));
bytes32 previousBlockHash = extract32(header, BlockNumberLength + TxNumberLength);
bytes32 merkleRootHash = extract32(header, BlockNumberLength + TxNumberLength + PreviousHashLength);
uint8 v = uint8(extract1(header, BlockNumberLength + TxNumberLength + PreviousHashLength + MerkleRootHashLength));
bytes32 r = extract32(header, BlockNumberLength + TxNumberLength + PreviousHashLength + MerkleRootHashLength + 1);
bytes32 s = extract32(header, BlockNumberLength + TxNumberLength + PreviousHashLength + MerkleRootHashLength + 33);
uint256 newBlockNumber = uint256(uint32(blockNumber));
require(newBlockNumber == lastBlockNumber+1);
if (lastBlockNumber != 0) {
Header storage previousHeader = headers[lastBlockNumber];
bytes32 previousHash = keccak256(PersonalMessagePrefixBytes, PreviousBlockPersonalHashLength.uintToBytes(), previousHeader.blockNumber, previousHeader.numTransactions, previousHeader.previousBlockHash, previousHeader.merkleRootHash,
previousHeader.v, previousHeader.r,previousHeader.s);
require(previousHash == previousBlockHash);
}
bytes32 newBlockHash = keccak256(PersonalMessagePrefixBytes, NewBlockPersonalHashLength.uintToBytes(), blockNumber, numTransactions, previousBlockHash, merkleRootHash);
if (v < 27) {
v = v+27;
}
address signer = ecrecover(newBlockHash, v, r, s);
require(operators[signer]);
Header memory newHeader = Header({
blockNumber: blockNumber,
numTransactions: numTransactions,
previousBlockHash: previousBlockHash,
merkleRootHash: merkleRootHash,
v: v,
r: r,
s: s
});
lastBlockNumber = lastBlockNumber+1;
headers[lastBlockNumber] = newHeader;
HeaderSubmittedEvent(signer, blockNumber, newBlockHash);
return true;
}
// ----------------------------------
// Deposit related functions
enum DepositStatus {
NoRecord,
Deposited,
WithdrawStarted,
WithdrawChallenged,
WithdrawCompleted,
DepositConfirmed
}
struct DepositRecord {
address from;
DepositStatus status;
uint256 amount;
uint256 index;
uint256 withdrawStartedTime;
}
event DepositEvent(address indexed _from, uint256 indexed _amount, uint256 indexed _depositIndex);
event DepositWithdrawStartedEvent(uint256 indexed _depositIndex);
event DepositWithdrawChallengedEvent(uint256 indexed _depositIndex);
event DepositWithdrawCompletedEvent(uint256 indexed _depositIndex);
mapping (address => uint256[]) userDepositRecords;
mapping (uint256 => mapping(uint256 => DepositRecord)) public depositRecords;
function () payable external {
deposit();
}
function depositRecordsForUser(address _user) public view returns (uint256[]) {
return userDepositRecords[_user];
}
function deposit() payable public returns (uint256 idx) {
if (block.number != lastEthBlockNumber) {
depositCounterInBlock = 0;
}
uint256 depositIndex = block.number << 32 + depositCounterInBlock;
DepositRecord storage record = depositRecords[0][depositIndex];
require(record.index == 0);
require(record.status == DepositStatus.NoRecord);
record.index = depositIndex;
record.from = msg.sender;
record.amount = msg.value;
record.status = DepositStatus.Deposited;
depositCounterInBlock = depositCounterInBlock + 1;
userDepositRecords[msg.sender].push(depositIndex);
DepositEvent(msg.sender, msg.value, depositIndex);
return depositIndex;
}
// function startDepositWithdraw(uint256 depositIndex) public returns (bool success) {
// DepositRecord storage record = depositRecords[0][depositIndex];
// require(record.index == depositIndex);
// require(record.status == DepositStatus.Deposited);
// require(record.from == msg.sender);
// record.status = DepositStatus.WithdrawStarted;
// record.withdrawStartedTime = now;
// DepositWithdrawStartedEvent(depositIndex);
// return true;
// }
// function finalizeDepositWithdraw(uint256 depositIndex) public returns (bool success) {
// DepositRecord storage record = depositRecords[0][depositIndex];
// require(record.index == depositIndex);
// require(record.status == DepositStatus.WithdrawStarted);
// require(now >= record.withdrawStartedTime + (24 hours));
// record.status = DepositStatus.WithdrawCompleted;
// DepositWithdrawCompletedEvent(depositIndex);
// record.from.transfer(record.amount);
// return true;
// }
// function challengeDepositWithdraw(uint256 depositIndex,
// uint32 _plasmaBlockNumber,
// bytes _plasmaTransaction,
// bytes _merkleProof) public returns (bool success) {
// DepositRecord storage record = depositRecords[0][depositIndex];
// require(record.index == depositIndex);
// require(record.status == DepositStatus.WithdrawStarted);
// record.status = DepositStatus.WithdrawChallenged;
// Header storage header = headers[uint256(_plasmaBlockNumber)];
// require(uint32(header.blockNumber) > 0);
// bool validProof = checkProof(header.merkleRootHash, _plasmaTransaction, _merkleProof, true);
// require(validProof);
// PlasmaTransaction memory TX = plasmaTransactionFromBytes(_plasmaTransaction);
// require(TX.txType == TxTypeFund);
// address signer = recoverTXsigner(_plasmaTransaction, TX.v, TX.r, TX.s, TX.txType);
// require(operators[signer]);
// TransactionOutput memory output0 = TX.outputs[0];
// TransactionOutput memory output1 = TX.outputs[1];
// require(output0.recipient == record.from);
// require(output0.amount == record.amount);
// require(output1.outputNumberInTX == 255);
// require(output1.amount == depositIndex);
// record.status = DepositStatus.DepositConfirmed;
// DepositWithdrawChallengedEvent(depositIndex);
// return true;
// }
// ----------------------------------
// Withdrawrelated functions
enum WithdrawStatus {
NoRecord,
Started,
Challenged,
Completed,
Rejected
}
struct WithdrawRecord {
uint256 index;
uint32 blockNumber;
uint32 txNumberInBlock;
uint8 outputNumberInTX;
address beneficiary;
bool isExpress;
WithdrawStatus status;
uint256 amount;
uint256 timeStarted;
uint256 timeEnded;
}
event WithdrawStartedEvent(uint32 indexed _blockNumber,
uint32 indexed _txNumberInBlock,
uint8 indexed _outputNumberInTX);
event WithdrawRequestAcceptedEvent(address indexed _from,
uint256 indexed _withdrawIndex);
event WithdrawFinalizedEvent(uint32 indexed _blockNumber,
uint32 indexed _txNumberInBlock,
uint8 indexed _outputNumberInTX);
event ExpressWithdrawMadeEvent(uint32 indexed _withdrawTxBlockNumber,
uint32 indexed _withdrawTxNumberInBlock,
address indexed _from);
mapping (address => uint256[]) userWithdrawRecords;
mapping (uint256 => mapping(uint256 => WithdrawRecord)) public withdrawRecords;
function withdrawRecordsForUser(address _user) public view returns (uint256[]) {
return userWithdrawRecords[_user];
}
// function startWithdraw(uint32 _plasmaBlockNumber, //references and proves ownership on output of original transaction
// uint32 _plasmaTxNumInBlock,
// uint8 _outputNumber,
// bytes _plasmaTransaction,
// bytes _merkleProof)
// public returns(bool success, uint256 withdrawIndex) {
// Header storage header = headers[uint256(_plasmaBlockNumber)];
// require(uint32(header.blockNumber) > 0);
// bool validProof = checkProof(header.merkleRootHash, _plasmaTransaction, _merkleProof, true);
// require(validProof);
// PlasmaTransaction memory TX = plasmaTransactionFromBytes(_plasmaTransaction);
// require(TX.txType != TxTypeWithdraw);
// address signer = recoverTXsigner(_plasmaTransaction, TX.v, TX.r, TX.s, TX.txType);
// require(signer != address(0));
// TransactionOutput memory output = TX.outputs[_outputNumber];
// require(output.recipient == msg.sender);
// require(output.outputNumberInTX != 255);
// WithdrawRecord storage record = populateWithdrawRecordFromOutput(output, _plasmaBlockNumber, _plasmaTxNumInBlock, _outputNumber);
// record.beneficiary = output.recipient;
// record.timeEnded = now;
// WithdrawRequestAcceptedEvent(output.recipient, record.index);
// WithdrawStartedEvent(_plasmaBlockNumber, _plasmaTxNumInBlock, _outputNumber);
// userWithdrawRecords[msg.sender].push(record.index);
// return (true, withdrawIndex);
// }
function makeWithdrawExpress(uint32 _plasmaBlockNumber, //references and proves ownership on withdraw transaction
uint32 _plasmaTxNumInBlock,
bytes _plasmaTransaction,
bytes _merkleProof)
public returns(bool success, uint256 withdrawIndex) {
Header storage header = headers[uint256(_plasmaBlockNumber)];
require(uint32(header.blockNumber) > 0);
bool validProof = checkProof(header.merkleRootHash, _plasmaTransaction, _merkleProof, true);
require(validProof);
PlasmaTransaction memory TX = plasmaTransactionFromBytes(_plasmaTransaction);
require(TX.txType == TxTypeWithdraw);
require(TX.txNumberInBlock == _plasmaTxNumInBlock);
address signer = recoverTXsigner(_plasmaTransaction, TX.v, TX.r, TX.s, TX.txType);
require(signer == msg.sender);
TransactionInput memory input = TX.inputs[0];
WithdrawRecord storage record = populateWithdrawRecordForInput(input);
record.beneficiary = signer;
require(record.status == WithdrawStatus.Started);
record.status = WithdrawStatus.Completed;
record.isExpress = true;
record.timeEnded = now;
WithdrawRequestAcceptedEvent(record.beneficiary, record.index);
WithdrawStartedEvent(input.blockNumber, input.txNumberInBlock, input.outputNumberInTX);
WithdrawFinalizedEvent(input.blockNumber, input.txNumberInBlock, input.outputNumberInTX);
ExpressWithdrawMadeEvent(_plasmaBlockNumber, TX.txNumberInBlock, record.beneficiary);
userWithdrawRecords[msg.sender].push(record.index);
signer.transfer(record.amount);
return (true, withdrawIndex);
}
// function getWithdrawRecordForInput(TransactionInput memory _input) internal view returns (WithdrawRecord storage record) {
// uint256 withdrawIndex = makeTransactionIndex(_input.blockNumber, _input.txNumberInBlock, _input.outputNumberInTX);
// withdrawIndex = withdrawIndex + (block.number << 128);
// record = withdrawRecords[0][withdrawIndex];
// require(record.index == withdrawIndex);
// require(record.blockNumber == _input.blockNumber);
// require(record.txNumberInBlock == _input.txNumberInBlock);
// require(record.outputNumberInTX == _input.outputNumberInTX);
// require(record.amount == _input.amount);
// return record;
// }
function populateWithdrawRecordForInput(TransactionInput memory _input) internal returns (WithdrawRecord storage record) {
uint256 withdrawIndex = makeTransactionIndex(_input.blockNumber, _input.txNumberInBlock, _input.outputNumberInTX);
// withdrawIndex = withdrawIndex + (block.number << 128);
record = withdrawRecords[0][withdrawIndex];
require(record.status == WithdrawStatus.NoRecord);
record.index = withdrawIndex;
record.blockNumber = _input.blockNumber;
record.txNumberInBlock = _input.txNumberInBlock;
record.outputNumberInTX = _input.outputNumberInTX;
record.status = WithdrawStatus.Started;
record.amount = _input.amount;
record.timeStarted = now;
return record;
}
function populateWithdrawRecordFromOutput(TransactionOutput memory _output, uint32 _blockNumber, uint32 _txNumberInBlock, uint8 _outputNumberInTX) internal returns (WithdrawRecord storage record) {
uint256 withdrawIndex = makeTransactionIndex(_blockNumber, _txNumberInBlock, _outputNumberInTX);
// withdrawIndex = withdrawIndex + (block.number << 128);
record = withdrawRecords[0][withdrawIndex];
require(record.status == WithdrawStatus.NoRecord);
record.index = withdrawIndex;
record.status = WithdrawStatus.Started;
record.amount = _output.amount;
record.timeStarted = now;
record.blockNumber = _blockNumber;
record.txNumberInBlock = _txNumberInBlock;
record.outputNumberInTX = _outputNumberInTX;
return record;
}
// function finalizeWithdraw(uint256 withdrawIndex) public returns(bool success) {
// WithdrawRecord storage record = withdrawRecords[0][withdrawIndex];
// require(record.status == WithdrawStatus.Started);
// require(now >= record.timeStarted + (24 hours));
// address to = record.beneficiary;
// record.status = WithdrawStatus.Completed;
// record.timeEnded = now;
// WithdrawFinalizedEvent(record.blockNumber, record.txNumberInBlock, record.outputNumberInTX);
// to.transfer(record.amount);
// return true;
// }
// ----------------------------------
// Double-spend related functions
struct DoubleSpendRecord {
bool prooved;
}
struct SpendAndWithdrawRecord {
bool prooved;
}
event DoubleSpendProovedEvent(uint256 indexed _txIndex1, uint256 indexed _txIndex2);
event SpendAndWithdrawProovedEvent(uint256 indexed _txIndex, uint256 indexed _withdrawIndex);
mapping (uint256 => mapping(uint256 => DoubleSpendRecord)) public doubleSpendRecords;
mapping (uint256 => mapping(uint256 => SpendAndWithdrawRecord)) public spendAndWithdrawRecords;
// two transactions spend the same output
function proveDoubleSpend(uint32 _plasmaBlockNumber1, //references and proves transaction number 1
uint32 _plasmaTxNumInBlock1,
uint8 _inputNumber1,
bytes _plasmaTransaction1,
bytes _merkleProof1,
uint32 _plasmaBlockNumber2, //references and proves transaction number 2
uint32 _plasmaTxNumInBlock2,
uint8 _inputNumber2,
bytes _plasmaTransaction2,
bytes _merkleProof2) public returns (bool success) {
uint256 index1 = makeTransactionIndex(_plasmaBlockNumber1, _plasmaTxNumInBlock1, _inputNumber1);
uint256 index2 = makeTransactionIndex(_plasmaBlockNumber2, _plasmaTxNumInBlock2, _inputNumber2);
require(index1 != index2);
require(!doubleSpendRecords[index1][index2].prooved);
require(!doubleSpendRecords[index2][index1].prooved);
require(checkActualDoubleSpendProof(_plasmaBlockNumber1,
_plasmaTxNumInBlock1,
_inputNumber1,
_plasmaTransaction1,
_merkleProof1,
_plasmaBlockNumber2,
_plasmaTxNumInBlock2,
_inputNumber2,
_plasmaTransaction2,
_merkleProof2));
doubleSpendRecords[index1][index2].prooved = true;
doubleSpendRecords[index2][index1].prooved = true;
return true;
}
function checkActualDoubleSpendProof (uint32 _plasmaBlockNumber1, //references and proves transaction number 1
uint32 _plasmaTxNumInBlock1,
uint8 _inputNumber1,
bytes _plasmaTransaction1,
bytes _merkleProof1,
uint32 _plasmaBlockNumber2, //references and proves transaction number 2
uint32 _plasmaTxNumInBlock2,
uint8 _inputNumber2,
bytes _plasmaTransaction2,
bytes _merkleProof2) public view returns (bool success) {
var (signer1, input1) = getTXinputDetailsFromProof(_plasmaBlockNumber1, _plasmaTxNumInBlock1, _inputNumber1, _plasmaTransaction1, _merkleProof1);
var (signer2, input2) = getTXinputDetailsFromProof(_plasmaBlockNumber2, _plasmaTxNumInBlock2, _inputNumber2, _plasmaTransaction2, _merkleProof2);
require(signer1 != address(0));
require(signer2 != address(0));
require(signer1 == signer2);
require(input1.blockNumber == input2.blockNumber);
require(input1.txNumberInBlock == input2.txNumberInBlock);
require(input1.outputNumberInTX == input2.outputNumberInTX);
return true;
}
// transaction output is withdrawn (witthout express process) and spent in Plasma chain
function proveSpendAndWithdraw(uint32 _plasmaBlockNumber, //references and proves transaction
uint32 _plasmaTxNumInBlock,
uint8 _inputNumber,
bytes _plasmaTransaction,
bytes _merkleProof,
uint256 _withdrawIndex //references withdraw
) public returns (bool success) {
uint256 txIndex = makeTransactionIndex(_plasmaBlockNumber, _plasmaTxNumInBlock, _inputNumber);
require(!spendAndWithdrawRecords[txIndex][_withdrawIndex].prooved);
WithdrawRecord storage record = withdrawRecords[0][_withdrawIndex];
var (signer, input) = getTXinputDetailsFromProof(_plasmaBlockNumber, _plasmaTxNumInBlock, _inputNumber, _plasmaTransaction, _merkleProof);
require(signer != address(0));
require(input.blockNumber == record.blockNumber);
require(input.txNumberInBlock == record.txNumberInBlock);
require(input.outputNumberInTX == record.outputNumberInTX);
if (record.status == WithdrawStatus.Completed) {
spendAndWithdrawRecords[txIndex][_withdrawIndex].prooved = true;
SpendAndWithdrawProovedEvent(txIndex, _withdrawIndex);
} else if (record.status == WithdrawStatus.Started) {
record.status = WithdrawStatus.Challenged;
spendAndWithdrawRecords[txIndex][_withdrawIndex].prooved = true;
SpendAndWithdrawProovedEvent(txIndex, _withdrawIndex);
}
return true;
}
// ----------------------------------
// Prove unlawful funding transactions on Plasma
struct FundingWithoutDepositRecord {
bool prooved;
}
struct DoubleFundingRecord {
bool prooved;
}
mapping (uint256 => mapping(uint256 => FundingWithoutDepositRecord)) public fundingWithoutDepositRecords;
mapping (uint256 => mapping(uint256 => DoubleFundingRecord)) public doubleFundingRecords;
event FundingWithoutDepositEvent(uint256 indexed _txIndex, uint256 indexed _depositIndex);
event DoubleFundingEvent(uint256 indexed _txIndex1, uint256 indexed _txIndex2);
function proveFundingWithoutDeposit(uint32 _plasmaBlockNumber, //references and proves transaction
uint32 _plasmaTxNumInBlock,
bytes _plasmaTransaction,
bytes _merkleProof) public returns (bool success) {
Header storage header = headers[uint256(_plasmaBlockNumber)];
require(uint32(header.blockNumber) > 0);
bool validProof = checkProof(header.merkleRootHash, _plasmaTransaction, _merkleProof, true);
require(validProof);
PlasmaTransaction memory TX = plasmaTransactionFromBytes(_plasmaTransaction);
require(TX.txType == TxTypeFund);
address signer = recoverTXsigner(_plasmaTransaction, TX.v, TX.r, TX.s, TX.txType);
require(operators[signer]);
TransactionOutput memory output = TX.outputs[0];
TransactionOutput memory outputAux = TX.outputs[1];
require(outputAux.outputNumberInTX == 255);
require(TX.txNumberInBlock == _plasmaTxNumInBlock);
uint256 depositIndex = output.amount;
uint256 transactionIndex = makeTransactionIndex(_plasmaBlockNumber, TX.txNumberInBlock, 0);
require(!fundingWithoutDepositRecords[transactionIndex][depositIndex].prooved);
DepositRecord storage record = depositRecords[0][depositIndex];
if (record.status == DepositStatus.NoRecord) {
FundingWithoutDepositEvent(transactionIndex, depositIndex);
fundingWithoutDepositRecords[transactionIndex][depositIndex].prooved = true;
return true;
} else if (record.amount != output.amount || record.from != output.recipient) {
FundingWithoutDepositEvent(transactionIndex, depositIndex);
fundingWithoutDepositRecords[transactionIndex][depositIndex].prooved = true;
return true;
}
revert();
return false;
}
//prove double funding of the same
function proveDoubleFunding(uint32 _plasmaBlockNumber1, //references and proves transaction number 1
uint32 _plasmaTxNumInBlock1,
bytes _plasmaTransaction1,
bytes _merkleProof1,
uint32 _plasmaBlockNumber2, //references and proves transaction number 2
uint32 _plasmaTxNumInBlock2,
bytes _plasmaTransaction2,
bytes _merkleProof2) public returns (bool success) {
var (signer1, depositIndex1, transactionIndex1) = getFundingTXdetailsFromProof(_plasmaBlockNumber1, _plasmaTxNumInBlock1, _plasmaTransaction1, _merkleProof1);
var (signer2, depositIndex2, transactionIndex2) = getFundingTXdetailsFromProof(_plasmaBlockNumber2, _plasmaTxNumInBlock2, _plasmaTransaction2, _merkleProof2);
require(checkDoubleFundingFromInternal(signer1, depositIndex1, transactionIndex1, signer2, depositIndex2, transactionIndex2));
doubleFundingRecords[transactionIndex1][transactionIndex2].prooved = true;
doubleFundingRecords[transactionIndex2][transactionIndex1].prooved = true;
return true;
}
function checkDoubleFundingFromInternal (address signer1,
uint256 depositIndex1,
uint256 transactionIndex1,
address signer2,
uint256 depositIndex2,
uint256 transactionIndex2) public view returns (bool) {
require(operators[signer1]);
require(operators[signer2]);
require(depositIndex1 == depositIndex2);
require(transactionIndex1 != transactionIndex2);
require(!doubleFundingRecords[transactionIndex1][transactionIndex2].prooved);
require(!doubleFundingRecords[transactionIndex2][transactionIndex1].prooved);
return true;
}
// ----------------------------------
// Convenience functions
function getTXinputDetailsFromProof(uint32 _plasmaBlockNumber,
uint32 _plasmaTxNumInBlock,
uint8 _inputNumber,
bytes _plasmaTransaction,
bytes _merkleProof) internal view returns (address signer, TransactionInput memory input) {
Header storage header = headers[uint256(_plasmaBlockNumber)];
require(uint32(header.blockNumber) > 0);
bool validProof = checkProof(header.merkleRootHash, _plasmaTransaction, _merkleProof, true);
require(validProof);
PlasmaTransaction memory TX = plasmaTransactionFromBytes(_plasmaTransaction);
require(TX.txType != TxTypeFund);
signer = recoverTXsigner(_plasmaTransaction, TX.v, TX.r, TX.s, TX.txType);
require(signer != address(0));
input = TX.inputs[uint256(_inputNumber)];
}
function getFundingTXdetailsFromProof(uint32 _plasmaBlockNumber,
uint32 _plasmaTxNumInBlock,
bytes _plasmaTransaction,
bytes _merkleProof) internal view returns (address signer, uint256 depositIndex, uint256 transactionIndex) {
Header storage header = headers[uint256(_plasmaBlockNumber)];
require(uint32(header.blockNumber) > 0);
bool validProof = checkProof(header.merkleRootHash, _plasmaTransaction, _merkleProof, true);
require(validProof);
PlasmaTransaction memory TX = plasmaTransactionFromBytes(_plasmaTransaction);
require(TX.txType == TxTypeFund);
signer = recoverTXsigner(_plasmaTransaction, TX.v, TX.r, TX.s, TX.txType);
TransactionOutput memory outputAux = TX.outputs[1];
require(outputAux.outputNumberInTX == 255);
require(TX.txNumberInBlock == _plasmaTxNumInBlock);
depositIndex = outputAux.amount;
transactionIndex = makeTransactionIndex(_plasmaBlockNumber, TX.txNumberInBlock, 0);
return (signer, depositIndex, transactionIndex);
}
function plasmaTransactionFromBytes(bytes _rawTX) internal view returns (PlasmaTransaction memory TX) {
uint8 txType = uint8(extract1(_rawTX, TxNumberLength));
uint256 expectedLength = TxLengthForType[txType];
require(_rawTX.length == expectedLength);
uint256 numInputs = NumInputsForType[txType];
uint256 numOutputs = NumOutputsForType[txType];
uint32 numInBlock = uint32(extract4(_rawTX,0));
uint256 signatureOffset = TxNumberLength + TxTypeLength + numInputs*TransactionInputLength + numOutputs*TransactionOutputLength;
uint8 v = uint8(extract1(_rawTX, signatureOffset));
bytes32 r = extract32(_rawTX, signatureOffset + 1);
bytes32 s = extract32(_rawTX, signatureOffset + 33);
TX = PlasmaTransaction({
txNumberInBlock: numInBlock,
txType: txType,
inputs: new TransactionInput[](numInputs),
outputs: new TransactionOutput[](numOutputs),
v : v,
r: r,
s: s
});
bytes memory insAndOutsSlice = _rawTX.slice(TxNumberLength + TxTypeLength, signatureOffset).toBytes();
assert(populateInsAndOuts(TX, numInputs, numOutputs, insAndOutsSlice));
return TX;
}
function populateInsAndOuts(PlasmaTransaction memory _TX, uint256 _numIns, uint256 _numOuts, bytes memory _insAndOutsSlice)
internal view returns (bool success) {
uint256 i;
for (i = 0; i < _numIns; i++) {
bytes memory rawInput = _insAndOutsSlice.slice(i*TransactionInputLength, (i+1)*TransactionInputLength).toBytes();
TransactionInput memory input = transactionInputFromBytes(rawInput);
_TX.inputs[i] = input;
}
for (i = 0; i < _numOuts; i++) {
bytes memory rawOutput = _insAndOutsSlice.slice(_numIns*TransactionInputLength + i*TransactionOutputLength,
_numIns*TransactionInputLength + (i+1)*TransactionOutputLength).toBytes();
TransactionOutput memory output = transactionOutputFromBytes(rawOutput);
if (output.outputNumberInTX == 255) {
continue;
}
require(output.outputNumberInTX == i);
_TX.outputs[i] = output;
}
return true;
}
function transactionInputFromBytes(bytes _rawInput) internal view returns(TransactionInput memory input) {
require(_rawInput.length == TransactionInputLength);
uint32 blockNumber = uint32(extract4(_rawInput,0));
uint32 txNumberInBlock = uint32(extract4(_rawInput, BlockNumberLength));
uint8 outputNumberInTX = uint8(extract1(_rawInput, BlockNumberLength + TxNumberLength));
uint256 amount = uint256(extract32(_rawInput, BlockNumberLength + TxNumberLength + TxOutputNumberLength));
input = TransactionInput({
blockNumber: blockNumber,
txNumberInBlock: txNumberInBlock,
outputNumberInTX: outputNumberInTX,
amount: amount
});
return input;
}
function transactionOutputFromBytes(bytes _rawOutput) internal view returns(TransactionOutput memory output) {
require(_rawOutput.length == TransactionOutputLength);
address recipient = address(extract20(_rawOutput, 0));
uint8 outputNumberInTX = uint8(extract1(_rawOutput, 20));
uint256 amount = uint256(extract32(_rawOutput, 20 + TxOutputNumberLength));
output = TransactionOutput({
recipient: recipient,
outputNumberInTX: outputNumberInTX,
amount: amount
});
return output;
}
function createPersonalMessageTypeHash(bytes memory message) internal view returns (bytes32 msgHash) {
// bytes memory prefixBytes = "\x19Ethereum Signed Message:\n";
bytes memory lengthBytes = message.length.uintToBytes();
// bytes memory prefix = prefixBytes.concat(lengthBytes);
bytes memory prefix = PersonalMessagePrefixBytes.concat(lengthBytes);
return keccak256(prefix, message);
}
function recoverTXsigner(bytes memory txData, uint8 v, bytes32 r, bytes32 s, uint256 txType) internal view returns (address signer) {
bytes memory sliceNoNumberNoSignatureParts = txData.slice(TxNumberLength, TxLengthForType[txType] - SignatureLength).toBytes();
bytes32 persMessageHashWithoutNumber = createPersonalMessageTypeHash(sliceNoNumberNoSignatureParts);
signer = ecrecover(persMessageHashWithoutNumber, v, r, s);
return signer;
}
function checkProof(bytes32 root, bytes data, bytes proof, bool convertToMessageHash) view public returns (bool) {
bytes32 h;
if (convertToMessageHash) {
h = createPersonalMessageTypeHash(data);
} else {
h = keccak256(data);
}
bytes32 elProvided;
uint8 rightElementProvided;
uint32 loc;
uint32 elLoc;
for (uint32 i = 32; i <= uint32(proof.length); i += 33) {
assembly {
loc := proof
elLoc := add(loc, add(i, 1))
elProvided := mload(elLoc)
}
rightElementProvided = uint8(bytes1(0xff)&proof[i-32]);
if (rightElementProvided > 0) {
h = keccak256(h, elProvided);
} else {
h = keccak256(elProvided, h);
}
}
return h == root;
}
function makeTransactionIndex(uint32 _blockNumber, uint32 _txNumberInBlock, uint8 _outputNumberInTX) pure public returns (uint256 index) {
index = uint256(_blockNumber) << ((TxNumberLength + TxTypeLength)*8) + uint256(_txNumberInBlock) << (TxTypeLength*8) + uint256(_outputNumberInTX);
return index;
}
}
================================================
FILE: lib/Block/block.js
================================================
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength} = require('../dataStructureLengths');
const ethUtil = require('ethereumjs-util');
const BN = ethUtil.BN
const {PlasmaTransaction,
TxTypeFund,
TxTypeMerge,
TxTypeSplit,
TxTypeWithdraw,
TxTypeTransfer,
TxLengthForType} = require('../Tx/tx');
const MerkleTools = require('../merkle-tools');
const {BlockHeader, BlockHeaderLength, BlockHeaderNumItems} = require('./blockHeader');
const assert = require('assert');
const stripHexPrefix = require('strip-hex-prefix');
// secp256k1n/2
const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16);
class Block {
constructor (data) {
if (data instanceof Object && data.constructor === Object ){
this.blockNumber = data.blockNumber || Buffer.alloc(blockNumberLength);
this.parentHash = data.parentHash || Buffer.alloc(previousHashLength);
this.transactions = data.transactions || [];
this.numberOfTransactions = data.transactions.length || 0;
const numberOfTransactionsBuffer = ethUtil.setLengthLeft(ethUtil.toBuffer(this.numberOfTransactions), txNumberLength);
assert(this.transactions && Array.isArray(this.transactions), "TXs should be an array");
const treeOptions = {
hashType: 'sha3'
}
this.merkleTree = new MerkleTools(treeOptions)
for (let i = 0; i < this.transactions.length; i++) {
const tx = this.transactions[i];
const txHash = tx.hash(true, true);
// console.log("Added tx hash " + ethUtil.bufferToHex(txHash));
// console.log("TX with content " + ethUtil.bufferToHex(Buffer.concat(tx.clearRaw(true, true))));
this.merkleTree.addLeaf(txHash);
}
assert (this.merkleTree.getLeafCount() == this.numberOfTransactions);
this.merkleTree.makeTree(false);
const rootValue = this.merkleTree.getMerkleRoot();
// console.log("Merkle root of block is " + ethUtil.bufferToHex(rootValue));
const headerParams = {
blockNumber: this.blockNumber,
parentHash: this.parentHash,
merkleRootHash: rootValue,
numberOfTransactions: numberOfTransactionsBuffer
}
this.header = new BlockHeader(headerParams);
} else if (Buffer.isBuffer(data)) {
this.transactions = [];
const head = data.slice(0, BlockHeaderLength);
let i = 0;
const headerArray = [];
for (let sliceLen of [blockNumberLength, txNumberLength, previousHashLength, merkleRootLength, signatureVlength, signatureRlength, signatureSlength]) {
headerArray.push(head.slice(i, i + sliceLen));
i += sliceLen;
}
this.header = new BlockHeader(headerArray);
let transactionsBuffer = data.slice(BlockHeaderLength, data.length);
while (transactionsBuffer.length > 0) {
const txType = ethUtil.bufferToInt(transactionsBuffer.slice(txNumberLength,txNumberLength + txTypeLength))
const txBin = transactionsBuffer.slice(0, TxLengthForType[txType]);
const TX = PlasmaTransaction.prototype.initTxForTypeFromBinary(txType, txBin);
this.transactions.push(TX);
transactionsBuffer = transactionsBuffer.slice(TxLengthForType[txType], transactionsBuffer.length);
}
assert(this.transactions.length == ethUtil.bufferToInt(head.slice(blockNumberLength,blockNumberLength+txNumberLength)));
const treeOptions = {
hashType: 'sha3'
}
this.merkleTree = new MerkleTools(treeOptions)
for (let j = 0; j < this.transactions.length; j++) {
const tx = this.transactions[j];
const txHash = tx.hash(true, true);
// console.log("Added tx hash " + ethUtil.bufferToHex(txHash));
// console.log("TX with content " + ethUtil.bufferToHex(Buffer.concat(tx.clearRaw(true, true))));
this.merkleTree.addLeaf(txHash);
}
assert(this.merkleTree.getLeafCount() == ethUtil.bufferToInt(head.slice(blockNumberLength,blockNumberLength+txNumberLength)));
this.merkleTree.makeTree(false);
const rootValue = this.merkleTree.getMerkleRoot();
assert(rootValue.equals(this.header.merkleRootHash))
assert(this.header.validate());
}
Object.defineProperty(this, 'from', {
enumerable: true,
configurable: true,
get: this.getSenderAddress.bind(this)
})
Object.defineProperty(this, 'raw', {
get: function () {
return this.serialize(false)
}
})
}
serializeSignature(signatureString) {
this.header.serializeSignature(signatureString);
const signature = stripHexPrefix(signatureString);
}
serialize(includeSignature) {
var txRaws = [];
this.transactions.forEach((tx) => {
const r = tx.clearRaw(true, true);
// const r = tx.raw.filter((f) => {
// return (typeof f !== 'undefined')
// })
txRaws.push(Buffer.concat(r))
})
return this.header.raw.concat(txRaws);
}
clearRaw(includeSignature) {
return this.header.clearRaw(includeSignature);
}
/**
* Computes a sha3-256 hash of the serialized tx
* @param {Boolean} [includeSignature=true] whether or not to inculde the signature
* @return {Buffer}
*/
hash (includeSignature) {
return this.header.hash(includeSignature)
}
/**
* returns the sender's address
* @return {Buffer}
*/
getSenderAddress () {
return this.header.getSenderAddress()
}
/**
* returns the public key of the sender
* @return {Buffer}
*/
getSenderPublicKey () {
return this.header._senderPubKey
}
getMerkleHash () {
return this.header.merkleRootHash;
}
/**
* Determines if the signature is valid
* @return {Boolean}
*/
verifySignature () {
return this.header.verifySignature()
}
/**
* sign a transaction with a given a private key
* @param {Buffer} privateKey
*/
sign (privateKey) {
this.header.sign(privateKey)
}
/**
* validates the signature and checks to see if it has enough gas
* @param {Boolean} [stringError=false] whether to return a string with a dscription of why the validation failed or return a Bloolean
* @return {Boolean|String}
*/
validate (stringError) {
const errors = []
if (!this.verifySignature()) {
errors.push('Invalid Signature')
}
if (stringError === undefined || stringError === false) {
return errors.length === 0
} else {
return errors.join(' ')
}
}
}
Block.prototype.toJSON = function (labeled) {
if (labeled) {
var obj = {
header: this.header.toJSON(labeled),
transactions: []
}
this.transactions.forEach(function (tx) {
const txJSON = tx.toJSON(labeled)
obj.transactions.push(txJSON);
})
return obj
} else {
return ethUtil.baToJSON(this.raw)
}
}
Block.prototype.toFullJSON = function (labeled) {
if (labeled) {
var obj = {
header: this.header.toFullJSON(labeled),
transactions: []
}
this.transactions.forEach(function (tx) {
const txJSON = tx.toFullJSON(labeled)
obj.transactions.push(txJSON);
})
return obj
} else {
return ethUtil.baToJSON(this.raw)
}
}
module.exports = Block
================================================
FILE: lib/Block/blockHeader.js
================================================
const {blockNumberLength,
txNumberLength,
txTypeLength,
signatureVlength,
signatureRlength,
signatureSlength,
merkleRootLength,
previousHashLength} = require('../dataStructureLengths');
const stripHexPrefix = require('strip-hex-prefix');
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const defineProperties = require('../serialize').defineProperties;
// secp256k1n/2
const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16)
class BlockHeader {
constructor (data) {
data = data || {}
// Define Properties
const fields = [{
name: 'blockNumber',
alias: 'block',
length: blockNumberLength,
allowLess: false,
default: Buffer.alloc(blockNumberLength)
}, {
name: 'numberOfTransactions',
alias: 'numTX',
length: txNumberLength,
allowLess: false,
default: Buffer.alloc(txNumberLength)
}, {
name: 'parentHash',
allowZero: true,
length: previousHashLength,
allowLess: false,
default: Buffer.alloc(previousHashLength)
}, {
name: 'merkleRootHash',
allowZero: true,
alias: 'merkle',
length: merkleRootLength,
allowLess: false,
default: Buffer.alloc(merkleRootLength)
}, {
name: 'v',
allowZero: true,
length: signatureVlength,
allowLess: false,
default: Buffer.alloc(signatureVlength)
}, {
name: 'r',
length: signatureRlength,
allowZero: true,
allowLess: false,
default: Buffer.alloc(signatureRlength)
}, {
name: 's',
length: signatureSlength,
allowZero: true,
allowLess: false,
default: Buffer.alloc(signatureSlength)
}]
defineProperties(this, fields, data)
/**
* @property {Buffer} from (read only) sender address of this transaction, mathematically derived from other parameters.
* @name from
* @memberof Transaction
*/
Object.defineProperty(this, 'from', {
enumerable: true,
configurable: true,
get: this.getSenderAddress.bind(this)
})
Object.defineProperty(this, 'length', {
enumerable: true,
configurable: true,
get: (() => Buffer.concat(this.raw).length)
})
}
/**
* Computes a sha3-256 hash of the serialized tx
* @param {Boolean} [includeSignature=true] whether or not to inculde the signature
* @return {Buffer}
*/
hash (includeSignature) {
if (includeSignature === undefined) includeSignature = true
// EIP155 spec:
// when computing the hash of a transaction for purposes of signing or recovering,
// instead of hashing only the first six elements (ie. nonce, gasprice, startgas, to, value, data),
// hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0
let items = this.clearRaw(includeSignature);
// return ethUtil.sha3(Buffer.concat(items));
return ethUtil.hashPersonalMessage(Buffer.concat(items));
}
serializeSignature(signatureString) {
const signature = stripHexPrefix(signatureString);
let r = ethUtil.addHexPrefix(signature.substring(0,64));
let s = ethUtil.addHexPrefix(signature.substring(64,128));
let v = ethUtil.addHexPrefix(signature.substring(128,130));
r = ethUtil.toBuffer(r);
s = ethUtil.toBuffer(s);
v = ethUtil.bufferToInt(ethUtil.toBuffer(v));
if (v < 27) {
v = v + 27;
}
v = ethUtil.toBuffer(v);
this.r = r;
this.v = v;
this.s = s;
}
/**
* returns the sender's address
* @return {Buffer}
*/
getSenderAddress () {
if (this._from) {
return this._from
}
const pubkey = this.getSenderPublicKey()
this._from = ethUtil.publicToAddress(pubkey)
return this._from
}
/**
* returns the public key of the sender
* @return {Buffer}
*/
getSenderPublicKey () {
if (!this._senderPubKey || !this._senderPubKey.length) {
if (!this.verifySignature()) throw new Error('Invalid Signature')
}
return this._senderPubKey
}
/**
* Determines if the signature is vali
* d
* @return {Boolean}
*/
verifySignature () {
const msgHash = this.hash(false)
// All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid.
if (new BN(this.s).cmp(N_DIV_2) === 1) {
return false
}
try {
let v = ethUtil.bufferToInt(this.v)
// if (this._chainId > 0) {
// v -= this._chainId * 2 + 8
// }
this._senderPubKey = ethUtil.ecrecover(msgHash, v, this.r, this.s)
} catch (e) {
return false
}
return !!this._senderPubKey
}
/**
* sign a transaction with a given a private key
* @param {Buffer} privateKey
*/
sign (privateKey) {
const msgHash = this.hash(false)
const sig = ethUtil.ecsign(msgHash, privateKey)
if (sig.v < 27){
sig.v += 27
}
Object.assign(this, sig)
}
clearRaw(includeSignature) {
let items
if (includeSignature) {
items = this.raw
} else {
items = this.raw.slice(0, this.raw.length-3)
}
return items;
// return Buffer.concat(items);
}
/**
* validates the signature and checks to see if it has enough gas
* @param {Boolean} [stringError=false] whether to return a string with a dscription of why the validation failed or return a Bloolean
* @return {Boolean|String}
*/
validate (stringError) {
const errors = []
if (!this.verifySignature()) {
errors.push('Invalid Signature')
}
if (stringError === undefined || stringError === false) {
return errors.length === 0
} else {
return errors.join(' ')
}
}
toFullJSON(labeled) {
if (labeled) {
const header = this.toJSON(labeled);
const blockNumber = ethUtil.bufferToInt(this.blockNumber)
const numberOfTransactions = ethUtil.bufferToInt(this.numberOfTransactions)
header.blockNumber = blockNumber
header.numberOfTransactions = nu
gitextract_q8j4tzls/ ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── Insomnia.json ├── LICENSE ├── README.md ├── app/ │ ├── config/ │ │ └── config.js │ ├── endpoints/ │ │ ├── acceptAndSignTransaction.js │ │ ├── acceptSignedTX.js │ │ ├── acceptTransaction.js │ │ ├── auxilary.js │ │ ├── createTransactionToSign.js │ │ ├── fundPlasma.js │ │ ├── getBlockByNumber.js │ │ ├── getTXsForAddress.js │ │ ├── getTxByNumber.js │ │ ├── getUTXOsForAddress.js │ │ ├── getWithdrawsForAddress.js │ │ ├── prepareProofForExpressWithdraw.js │ │ └── withdraw.js │ ├── helpers/ │ │ ├── checkSpendingTX.js │ │ ├── createFundingTransaction.js │ │ ├── createTxFromJson.js │ │ ├── createWithdrawTxFromJson.js │ │ ├── getAllTXsForAddress.js │ │ ├── getAllUTXOsForAddress.js │ │ ├── getAllWithdrawsForAddress.js │ │ ├── getBlock.js │ │ ├── getTX.js │ │ ├── getUTXO.js │ │ ├── hexDataToEncodedBytes.js │ │ ├── prepareProofForTX.js │ │ ├── processDepositEvent.js │ │ ├── processExpressWithdrawMadeEvent.js │ │ ├── processWithdrawFinalazedEvent.js │ │ ├── processWithdrawStartedEvent.js │ │ └── signatureChecks.js │ └── miner.js ├── compile.js ├── contracts/ │ └── PlasmaParent.sol ├── lib/ │ ├── Block/ │ │ ├── block.js │ │ └── blockHeader.js │ ├── Tx/ │ │ ├── input.js │ │ ├── output.js │ │ └── tx.js │ ├── dataStructureLengths.js │ ├── merkle-tools.js │ └── serialize.js ├── package.json └── server.js
SYMBOL INDEX (88 symbols across 15 files)
FILE: app/config/config.js
method port (line 16) | get port() {
method provider (line 22) | get provider() {
method deployedPlasmaContract (line 27) | get deployedPlasmaContract(){
method plasmaOperatorAddress (line 34) | get plasmaOperatorAddress() {
method plasmaOperatorPassword (line 39) | get plasmaOperatorPassword () {
FILE: app/helpers/getAllTXsForAddress.js
function getTXsforAddress (line 34) | async function getTXsforAddress(addressString, cb) {
function getTXsAddressWithIndex (line 79) | async function getTXsAddressWithIndex(addressString, cb) {
FILE: app/helpers/getAllUTXOsForAddress.js
function getUTXOforAddress (line 32) | async function getUTXOforAddress(addressString, cb) {
function getUTXOforAddressWithIndex (line 79) | async function getUTXOforAddressWithIndex(addressString, cb) {
FILE: app/helpers/getAllWithdrawsForAddress.js
function getTXsforAddress (line 35) | async function getTXsforAddress(addressString, cb) {
function getWithdrawsForAddress (line 80) | async function getWithdrawsForAddress(addressString, cb) {
FILE: app/helpers/signatureChecks.js
function signForECRECOVER (line 3) | function signForECRECOVER(dataBuffer, privKeyBuffer) {
function checkSender (line 9) | function checkSender(dataBuffer, signatureObject, address) {
function testSignature (line 16) | function testSignature() {
FILE: app/miner.js
function startBlockProcessing (line 43) | function startBlockProcessing(app, fromBlock) {
function createBlock (line 112) | async function createBlock() {
function writeBlock (line 207) | async function writeBlock(block) {
function startHeadersSubmission (line 258) | function startHeadersSubmission(app, fromBlock) {
function submitBlockHeader (line 308) | function submitBlockHeader(block){
function tryToMine (line 347) | async function tryToMine() {
function sleep (line 353) | function sleep(ms) {
FILE: compile.js
function sleep (line 14) | function sleep(ms) {
function findImports (line 23) | function findImports (path) {
function main (line 35) | async function main() {
FILE: lib/Block/block.js
constant N_DIV_2 (line 25) | const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501d...
class Block (line 27) | class Block {
method constructor (line 28) | constructor (data) {
method serializeSignature (line 110) | serializeSignature(signatureString) {
method serialize (line 115) | serialize(includeSignature) {
method clearRaw (line 127) | clearRaw(includeSignature) {
method hash (line 136) | hash (includeSignature) {
method getSenderAddress (line 144) | getSenderAddress () {
method getSenderPublicKey (line 152) | getSenderPublicKey () {
method getMerkleHash (line 156) | getMerkleHash () {
method verifySignature (line 164) | verifySignature () {
method sign (line 172) | sign (privateKey) {
method validate (line 182) | validate (stringError) {
FILE: lib/Block/blockHeader.js
constant N_DIV_2 (line 15) | const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501d...
class BlockHeader (line 17) | class BlockHeader {
method constructor (line 18) | constructor (data) {
method hash (line 90) | hash (includeSignature) {
method serializeSignature (line 104) | serializeSignature(signatureString) {
method getSenderAddress (line 125) | getSenderAddress () {
method getSenderPublicKey (line 138) | getSenderPublicKey () {
method verifySignature (line 150) | verifySignature () {
method sign (line 173) | sign (privateKey) {
method clearRaw (line 184) | clearRaw(includeSignature) {
method validate (line 200) | validate (stringError) {
method toFullJSON (line 212) | toFullJSON(labeled) {
FILE: lib/Tx/input.js
constant N_DIV_2 (line 16) | const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501d...
class TransactionInput (line 18) | class TransactionInput {
method constructor (line 19) | constructor (data) {
method getKey (line 80) | getKey() {
method toFullJSON (line 88) | toFullJSON(labeled) {
FILE: lib/Tx/output.js
constant N_DIV_2 (line 17) | const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501d...
class TransactionOutput (line 19) | class TransactionOutput {
method constructor (line 20) | constructor (data) {
method getKey (line 75) | getKey() {
method toFullJSON (line 83) | toFullJSON(labeled) {
FILE: lib/Tx/tx.js
constant N_DIV_2 (line 25) | const N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501d...
class PlasmaTransaction (line 33) | class PlasmaTransaction {
method constructor (line 34) | constructor (data) {
method toWithdrawAddress (line 111) | toWithdrawAddress () {
method fromFundingAddress (line 119) | fromFundingAddress () {
method hash (line 128) | hash (includeSignature, includeNumber) {
method serializeSignature (line 140) | serializeSignature(signatureString) {
method getTransactionInput (line 157) | getTransactionInput(inputNumber) {
method getTransactionOutput (line 165) | getTransactionOutput(outputNumber) {
method getKey (line 173) | getKey() {
method clearRaw (line 187) | clearRaw(includeNumber, includeSignature) {
method getSenderAddress (line 208) | getSenderAddress () {
method getSenderPublicKey (line 221) | getSenderPublicKey () {
method verifySignature (line 232) | verifySignature () {
method sign (line 256) | sign (privateKey) {
method assignNumber (line 265) | assignNumber (numberInBlock) {
method transactionTypeUInt (line 270) | transactionTypeUInt() {
method validate (line 280) | validate (stringError) {
method toFullJSON (line 306) | toFullJSON(labeled) {
FILE: lib/merkle-tools.js
function _getBuffer (line 202) | function _getBuffer (value) {
function _isHex (line 212) | function _isHex (value) {
function _calculateNextLevel (line 219) | function _calculateNextLevel (doubleHash) {
function _calculateBTCNextLevel (line 238) | function _calculateBTCNextLevel (doubleHash) {
FILE: lib/serialize.js
function getter (line 29) | function getter () {
function setter (line 32) | function setter (v) {
FILE: server.js
function startVM (line 59) | async function startVM(){
function deployContracts (line 88) | async function deployContracts() {
function jump (line 108) | function jump(duration) {
function prepareOracle (line 125) | async function prepareOracle(){
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (247K chars).
[
{
"path": ".gitignore",
"chars": 983,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directo"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5597,
"preview": "# Code of Conduct\n\n## 1. Purpose\n\nA primary goal of Plasma ETHexchange is to be inclusive to the largest number of contr"
},
{
"path": "CONTRIBUTING.md",
"chars": 640,
"preview": "# How to contribute\n\nBug reports and pull requests from users is what keeps this project working.\n\n## Basics\n\n1. Create "
},
{
"path": "ISSUE_TEMPLATE.md",
"chars": 392,
"preview": "To help us debug your issue please explain:\n- What you were trying to do (and why)\n- What happened (include command outp"
},
{
"path": "Insomnia.json",
"chars": 14268,
"preview": "{\n\t\"_type\": \"export\",\n\t\"__export_format\": 3,\n\t\"__export_date\": \"2017-11-10T15:02:45.828Z\",\n\t\"__export_source\": \"insomnia"
},
{
"path": "LICENSE",
"chars": 1089,
"preview": "MIT License\n\nCopyright (c) 2017 BANKEX - Proof-of-Asset Protocol\n\nPermission is hereby granted, free of charge, to any p"
},
{
"path": "README.md",
"chars": 9094,
"preview": ",\n blockPrefix : Buffer.from('blk'),\n headerPrefix : Buffer"
},
{
"path": "app/endpoints/acceptAndSignTransaction.js",
"chars": 2470,
"preview": "// For demo purposes only\nconst Web3 = require('web3');\nconst validateSchema = require('jsonschema').validate;\nconst con"
},
{
"path": "app/endpoints/acceptSignedTX.js",
"chars": 3246,
"preview": "// For demo purposes only\nconst Web3 = require('web3');\nconst validateSchema = require('jsonschema').validate;\nconst con"
},
{
"path": "app/endpoints/acceptTransaction.js",
"chars": 1646,
"preview": "const Web3 = require('web3');\nmodule.exports = function(app, levelDB, web3) {\n app.post('/sendPlasmaTransaction', 'se"
},
{
"path": "app/endpoints/auxilary.js",
"chars": 6256,
"preview": "const Web3 = require('web3');\nconst BN = Web3.utils.BN;\nconst validateSchema = require('jsonschema').validate;\nconst con"
},
{
"path": "app/endpoints/createTransactionToSign.js",
"chars": 2943,
"preview": "// For demo purposes only\nconst Web3 = require('web3');\nconst validateSchema = require('jsonschema').validate;\nconst con"
},
{
"path": "app/endpoints/fundPlasma.js",
"chars": 3909,
"preview": "const Web3 = require('web3');\nconst BN = Web3.utils.BN;\nconst ethUtil = require('ethereumjs-util');\nconst config = requi"
},
{
"path": "app/endpoints/getBlockByNumber.js",
"chars": 664,
"preview": "const Web3 = require('web3');\nmodule.exports = function(app, levelDB, web3) {\n const getBlockByNumber = require('../h"
},
{
"path": "app/endpoints/getTXsForAddress.js",
"chars": 1013,
"preview": "const Web3 = require('web3');\nconst ethUtil = require('ethereumjs-util'); \nconst assert = require('assert')\n\nmodule.expo"
},
{
"path": "app/endpoints/getTxByNumber.js",
"chars": 876,
"preview": "const Web3 = require('web3');\nmodule.exports = function(app, levelDB, web3) {\n const getTX = require('../helpers/getT"
},
{
"path": "app/endpoints/getUTXOsForAddress.js",
"chars": 1025,
"preview": "const Web3 = require('web3');\nconst ethUtil = require('ethereumjs-util'); \nconst assert = require('assert')\n\nmodule.expo"
},
{
"path": "app/endpoints/getWithdrawsForAddress.js",
"chars": 1044,
"preview": "const Web3 = require('web3');\nconst ethUtil = require('ethereumjs-util'); \nconst assert = require('assert')\n\nmodule.expo"
},
{
"path": "app/endpoints/prepareProofForExpressWithdraw.js",
"chars": 2005,
"preview": "//demo purposes only\nconst Web3 = require('web3');\nconst ethUtil = require('ethereumjs-util'); \nconst BN = ethUtil.BN;\nc"
},
{
"path": "app/endpoints/withdraw.js",
"chars": 4899,
"preview": "//demo purposes only\nconst Web3 = require('web3');\nconst ethUtil = require('ethereumjs-util'); \nconst BN = ethUtil.BN;\nc"
},
{
"path": "app/helpers/checkSpendingTX.js",
"chars": 3586,
"preview": "const config = require(\"../config/config\");\nconst blockPrefix = config.blockPrefix;\nconst utxoPrefix = config.utxoPrefix"
},
{
"path": "app/helpers/createFundingTransaction.js",
"chars": 2548,
"preview": "const config = require(\"../config/config\");\nconst ethUtil = require('ethereumjs-util'); \nconst BN = ethUtil.BN;\n\nconst {"
},
{
"path": "app/helpers/createTxFromJson.js",
"chars": 6775,
"preview": "const {blockNumberLength,\n txNumberLength,\n txTypeLength, \n signatureVlength,\n signatureRlength,\n signatu"
},
{
"path": "app/helpers/createWithdrawTxFromJson.js",
"chars": 5737,
"preview": "const {blockNumberLength,\n txNumberLength,\n txTypeLength, \n signatureVlength,\n signatureRlength,\n signatu"
},
{
"path": "app/helpers/getAllTXsForAddress.js",
"chars": 4701,
"preview": "const config = require(\"../config/config\");\nconst ethUtil = require('ethereumjs-util'); \nconst {blockPrefix, \n utxoIn"
},
{
"path": "app/helpers/getAllUTXOsForAddress.js",
"chars": 5535,
"preview": "const config = require(\"../config/config\");\nconst ethUtil = require('ethereumjs-util'); \nconst blockPrefix = config.bloc"
},
{
"path": "app/helpers/getAllWithdrawsForAddress.js",
"chars": 4764,
"preview": "const config = require(\"../config/config\");\nconst ethUtil = require('ethereumjs-util'); \nconst {blockPrefix, \n utxoIn"
},
{
"path": "app/helpers/getBlock.js",
"chars": 892,
"preview": "const config = require(\"../config/config\");\nconst blockPrefix = config.blockPrefix;\nconst Block = require(\"../../lib/Blo"
},
{
"path": "app/helpers/getTX.js",
"chars": 1645,
"preview": "const config = require(\"../config/config\");\nconst blockPrefix = config.blockPrefix;\nconst utxoPrefix = config.utxoPrefix"
},
{
"path": "app/helpers/getUTXO.js",
"chars": 1466,
"preview": "const config = require(\"../config/config\");\nconst blockPrefix = config.blockPrefix;\nconst utxoPrefix = config.utxoPrefix"
},
{
"path": "app/helpers/hexDataToEncodedBytes.js",
"chars": 427,
"preview": "const ethUtil = require('ethereumjs-util'); \nmodule.exports = function convertForRemix(hexString){\n const hex = ethUt"
},
{
"path": "app/helpers/prepareProofForTX.js",
"chars": 1459,
"preview": "//demo purposes only\nconst Web3 = require('web3');\nconst ethUtil = require('ethereumjs-util'); \nconst assert = require('"
},
{
"path": "app/helpers/processDepositEvent.js",
"chars": 1641,
"preview": "const createFundingTransaction = require('./createFundingTransaction');\nconst Web3 = require('web3');\nconst BN = Web3.ut"
},
{
"path": "app/helpers/processExpressWithdrawMadeEvent.js",
"chars": 1252,
"preview": "const createFundingTransaction = require('./createFundingTransaction');\nconst Web3 = require('web3');\nconst BN = Web3.ut"
},
{
"path": "app/helpers/processWithdrawFinalazedEvent.js",
"chars": 1423,
"preview": "const createFundingTransaction = require('./createFundingTransaction');\nconst Web3 = require('web3');\nconst BN = Web3.ut"
},
{
"path": "app/helpers/processWithdrawStartedEvent.js",
"chars": 0,
"preview": ""
},
{
"path": "app/helpers/signatureChecks.js",
"chars": 975,
"preview": "const ethUtil = require('ethereumjs-util'); \n\nfunction signForECRECOVER(dataBuffer, privKeyBuffer) {\n const hash = et"
},
{
"path": "app/miner.js",
"chars": 16874,
"preview": "const config = require(\"./config/config\");\nconst blockPrefix = config.blockPrefix;\nconst utxoPrefix = config.utxoPrefix;"
},
{
"path": "compile.js",
"chars": 2815,
"preview": "const assert = require('assert');\nvar fs = require(\"fs\");\nvar solc = require('solc');\nvar rimraf = require('rimraf');\nri"
},
{
"path": "contracts/PlasmaParent.sol",
"chars": 50060,
"preview": "pragma solidity ^0.4.18;\n\nlibrary ByteSlice {\n\n struct Slice {\n uint _unsafe_memPtr; // Memory address of th"
},
{
"path": "lib/Block/block.js",
"chars": 7608,
"preview": "const {blockNumberLength,\n txNumberLength,\n txTypeLength, \n signatureVlength,\n signatureRlength,\n signatu"
},
{
"path": "lib/Block/blockHeader.js",
"chars": 6291,
"preview": "const {blockNumberLength,\n txNumberLength,\n txTypeLength, \n signatureVlength,\n signatureRlength,\n signatureSlength,"
},
{
"path": "lib/Tx/input.js",
"chars": 3332,
"preview": "const {blockNumberLength,\ntxNumberLength,\ntxTypeLength, \nsignatureVlength,\nsignatureRlength,\nsignatureSlength,\nmerkleRoo"
},
{
"path": "lib/Tx/output.js",
"chars": 3330,
"preview": "const {blockNumberLength,\n txNumberLength,\n txTypeLength, \n signatureVlength,\n signatureRlength,\n signatu"
},
{
"path": "lib/Tx/tx.js",
"chars": 12229,
"preview": "const {blockNumberLength,\n txNumberLength,\n txTypeLength, \n signatureVlength,\n signatureRlength,\n signatureSlength,"
},
{
"path": "lib/dataStructureLengths.js",
"chars": 305,
"preview": "module.exports = {\n blockNumberLength: 4,\n txNumberLength: 4,\n txTypeLength :1, \n signatureVlength : 1,\n "
},
{
"path": "lib/merkle-tools.js",
"chars": 8993,
"preview": "'use strict'\n\n/* Copyright 2017 Tierion\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not "
},
{
"path": "lib/serialize.js",
"chars": 2743,
"preview": "const ethUtil = require('ethereumjs-util')\nconst assert = require('assert');\n\nexports.defineProperties = function (self,"
},
{
"path": "package.json",
"chars": 973,
"preview": "{\n \"name\": \"plasma\",\n \"version\": \"1.0.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/BankEx/P"
},
{
"path": "server.js",
"chars": 7441,
"preview": "const config = require('./app/config/config');\nvar rimraf = require('rimraf');\nif (!config.testOnRinkeby) {\n rimraf.s"
}
]
About this extraction
This page contains the full source code of the BankEx/PlasmaETHexchange GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (228.3 KB), approximately 56.8k tokens, and a symbol index with 88 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.