diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..0dc4a9c --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,8 @@ +# Usage +0. set the `appId` (Fluence) and `lazyAddress` (Eth contract) in `index.js` + +1. run ```npm i``` + +2. run ```npm run build``` + +3. run ```npm run start``` \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..22551f9 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,95 @@ + + + + + + Lazy snark dashboard + + + + + +
+
+

+

+ Lazy snark dashboard +

+

+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +

+
+
+
+
+ +
+
+ +
+
+ +

+
+
+
+
+ +
+
+ +
+
+ +

+ +
+
+
+
+ +
+
+ +
+
+ +

+
+
+
+
+ +
+
+ +
+
+ +

+
+
+
+ + + \ No newline at end of file diff --git a/frontend/index.js b/frontend/index.js new file mode 100644 index 0000000..bab5bc2 --- /dev/null +++ b/frontend/index.js @@ -0,0 +1,437 @@ +//CONNECT FLUENCE +import * as fluence from "fluence"; + + +window.getResultAsString = function (result) { + return result.result().then((r) => r.asString()) +}; + +var contractInstance; + +$(document).ready(function() { + let contractAddress = "0xeFF91455de6D4CF57C141bD8bF819E5f873c1A01"; + + // set ethUrl to `undefined` to use MetaMask instead of Ethereum node + let ethUrl = "http://rinkeby.fluence.one:8545/"; + + // application to interact with that stored in Fluence contract + let appId = "264"; + + // create a session between client and backend application, and then join the game + fluence.connect(contractAddress, appId, ethUrl).then((s) => { + console.log("Session created"); + window.session = s; + }); + + var lazyAddress = '0x1cca1f0be338c747b11a16aba8d0905251628bdf'; + let ControllerAbi = [ + { + "constant": true, + "inputs": [], + "name": "verifier", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stake", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "tasks", + "outputs": [ + { + "components": [ + { + "name": "input", + "type": "uint256[5]" + } + ], + "name": "data", + "type": "tuple" + }, + { + "components": [ + { + "name": "a", + "type": "uint256[2]" + }, + { + "name": "b", + "type": "uint256[2][2]" + }, + { + "name": "c", + "type": "uint256[2]" + } + ], + "name": "proof", + "type": "tuple" + }, + { + "name": "submitter", + "type": "address" + }, + { + "name": "timestamp", + "type": "uint256" + }, + { + "name": "status", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "index", + "type": "uint256" + }, + { + "components": [ + { + "components": [ + { + "name": "input", + "type": "uint256[5]" + } + ], + "name": "data", + "type": "tuple" + }, + { + "components": [ + { + "name": "a", + "type": "uint256[2]" + }, + { + "name": "b", + "type": "uint256[2][2]" + }, + { + "name": "c", + "type": "uint256[2]" + } + ], + "name": "proof", + "type": "tuple" + }, + { + "name": "submitter", + "type": "address" + }, + { + "name": "timestamp", + "type": "uint256" + }, + { + "name": "status", + "type": "uint8" + } + ], + "indexed": false, + "name": "task", + "type": "tuple" + } + ], + "name": "Submitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "challenger", + "type": "address" + }, + { + "indexed": false, + "name": "index", + "type": "uint256" + } + ], + "name": "Challenged", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "tasksNum", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "components": [ + { + "name": "input", + "type": "uint256[5]" + } + ], + "name": "data", + "type": "tuple" + }, + { + "components": [ + { + "name": "a", + "type": "uint256[2]" + }, + { + "name": "b", + "type": "uint256[2][2]" + }, + { + "name": "c", + "type": "uint256[2]" + } + ], + "name": "proof", + "type": "tuple" + } + ], + "name": "submit", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "id", + "type": "uint256" + } + ], + "name": "challenge", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "id", + "type": "uint256" + } + ], + "name": "finzalize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "id", + "type": "uint256" + } + ], + "name": "taskDataById", + "outputs": [ + { + "name": "data", + "type": "uint256[13]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "last5Timestamps", + "outputs": [ + { + "name": "result", + "type": "uint256[5]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "id", + "type": "uint256" + } + ], + "name": "getDataById", + "outputs": [ + { + "components": [ + { + "components": [ + { + "name": "input", + "type": "uint256[5]" + } + ], + "name": "data", + "type": "tuple" + }, + { + "components": [ + { + "name": "a", + "type": "uint256[2]" + }, + { + "name": "b", + "type": "uint256[2][2]" + }, + { + "name": "c", + "type": "uint256[2]" + } + ], + "name": "proof", + "type": "tuple" + }, + { + "name": "submitter", + "type": "address" + }, + { + "name": "timestamp", + "type": "uint256" + }, + { + "name": "status", + "type": "uint8" + } + ], + "name": "task", + "type": "tuple" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ]; + let ControllerContract = web3.eth.contract(ControllerAbi); + contractInstance = ControllerContract.at(lazyAddress); + window.ethereum.enable(); + + contractInstance.tasksNum(function (err, result) { + let maxLen = Math.min(result, 5); + for (let i = 0; i < maxLen; i++) { + let data = result - 1 - i; + $('#state-id-' + i).text(data); + let fluenceResponse_check = session.request(`{"action": "Check", "proof_id": ${data}}`); + getResultAsString(fluenceResponse_check).then(function (str) { + let fluenceResponse = JSON.parse(str); + console.log(fluenceResponse); + if (fluenceResponse.hasOwnProperty('verifed')) { + if (fluenceResponse.verifed) { + // все хорошо - мы проверили в флюенсе + $('#state-status-fluence-' + i).text('TRUE by Fluence.'); + $('#challenge-' + i).prop('disabled', true); + } else { + // мы проверили, пруф неправильный + $('#state-status-fluence-' + i).text('FALSE by Fluence.'); + $('#challenge-' + i).text('Challenge on Ethereum!') + } + } else { + console.log('Task N ' + data + ' is not checked on Fluence!') + } + }); + } + }); +}); + +$('button').click(function () { + let id = $(this)[0].id.slice(-1); + let data = $('#state-id-' + id)[0].textContent; + + if ($(this)[0].textContent === 'Challenge on Fluence!') { + contractInstance.taskDataById(data, function (err, result) { + let public_par = result.slice(0, 5); + let proof = result.slice(5, 13); + + let fluenceResponse_ver = session.request(`{"action": "Verify", "proof_id": ${data}, "public_par": [${public_par}], "proof": [${proof}]}`); + getResultAsString(fluenceResponse_ver).then(function (str) { + let fluenceResponse = JSON.parse(str); + let success = fluenceResponse.result === 1; + console.log(fluenceResponse); + if (success) { + // все хорошо - мы проверили в флюенсе + $('#state-status-fluence-' + id).text('TRUE by Fluence.'); + $('#challenge-' + id).prop('disabled', true); + } else { + // мы проверили, пруф неправильный + $('#state-status-fluence-' + id).text('FALSE by Fluence.'); + $('#challenge-' + id).text('Challenge on Ethereum!') + } + }); + }); + } else if ($(this)[0].textContent === 'Challenge on Ethereum!') { + challengeEthereum(id, data); + } else { + + } +}); + +function challengeEthereum(id, data) { + console.log('Challenging task N ' + data + ' on Ethereum!'); + contractInstance.challenge.sendTransaction(data, function (err, txHash) { + $('#challenge-' + id).remove(); + $('#link-' + id).text('See tx on Etherscan!').attr("href", "https://rinkeby.etherscan.io/tx/" + txHash); + + }); +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..337fe53 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,31 @@ +{ + "name": "frontend-challenger", + "version": "1.0.0", + "description": "", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "webpack-dev-server", + "build": "webpack" + }, + "files": [ + "index.html" + ], + "keywords": [], + "author": "", + "devDependencies": { + "copy-webpack-plugin": "^5.0.2", + "css-loader": "^2.1.1", + "html-webpack-plugin": "^3.2.0", + "style-loader": "0.23.1", + "webpack": "^4.29.6", + "webpack-cli": "^3.3.0", + "webpack-dev-server": "^3.2.1", + "web3": "1.0.0-beta.55" + }, + "dependencies": { + "Package": "0.0.1", + "fluence": "0.1.26" + } +} + diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js new file mode 100644 index 0000000..e755938 --- /dev/null +++ b/frontend/webpack.config.js @@ -0,0 +1,35 @@ +const path = require('path'); +const webpack = require('webpack'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = { + // use index.js as entrypoint + entry: { + app: ['./index.js'] + }, + devServer: { + contentBase: './bundle', + hot: true + }, + mode: "production", + module: { + rules: [ + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + } + ] + }, + // build all code in `bundle.js` in `bundle` directory + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'bundle') + }, + plugins: [ + // create `index.html` with imported `bundle.js` + new CopyWebpackPlugin([{ + from: './*.html' + }]), + new webpack.HotModuleReplacementPlugin() + ] +}; diff --git a/truffle/contracts/Lazy.sol b/truffle/contracts/Lazy.sol index 404b215..87cf7af 100644 --- a/truffle/contracts/Lazy.sol +++ b/truffle/contracts/Lazy.sol @@ -19,6 +19,9 @@ contract Lazy is Structs { } Task[] public tasks; + function tasksNum() external view returns(uint) { + return tasks.length; + } uint256 public stake; IVerifier public verifier; @@ -65,6 +68,28 @@ contract Lazy is Structs { msg.sender.transfer(stake); } + function taskDataById(uint id) external view returns( + uint[13] memory data + ) { + Task memory task = tasks[id]; + + data[0] = task.data.input[0]; + data[1] = task.data.input[1]; + data[2] = task.data.input[2]; + data[3] = task.data.input[3]; + data[4] = task.data.input[4]; + + data[5] = task.proof.a[0]; + data[6] = task.proof.a[1]; + + data[7] = task.proof.b[0][0]; + data[8] = task.proof.b[0][1]; + data[9] = task.proof.b[1][0]; + data[10] = task.proof.b[1][0]; + + data[11] = task.proof.c[0]; + data[12] = task.proof.c[1]; + } function last5Timestamps() view external returns (uint256[5] memory result) {