From e3138f8fdeb38d8d5a4cd9aa2a6eb67e05fa5f8f Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Thu, 20 Dec 2018 15:29:10 +1100 Subject: [PATCH 1/2] add yarn pow-metrics --- js/util_worker_tasks.js | 4 +- libloki/proof-of-work.js | 25 ++++---- libloki/test/metrics.js | 124 +++++++++++++++++++++++++++++++++++++++ metrics.html | 51 ++++++++++++++++ metrics_app.js | 61 +++++++++++++++++++ package.json | 3 +- 6 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 libloki/test/metrics.js create mode 100644 metrics.html create mode 100644 metrics_app.js diff --git a/js/util_worker_tasks.js b/js/util_worker_tasks.js index f09325fa1..4e9b873ce 100644 --- a/js/util_worker_tasks.js +++ b/js/util_worker_tasks.js @@ -41,6 +41,6 @@ function stringToArrayBufferBase64(string) { function arrayBufferToStringBase64(arrayBuffer) { return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); } -function calcPoW(timestamp, ttl, pubKey, data, development) { - return pow.calcPoW(timestamp, ttl, pubKey, data, development); +function calcPoW(timestamp, ttl, pubKey, data, development, nonceTrial = undefined, increment = 1, nonceStartValue = 0) { + return pow.calcPoW(timestamp, ttl, pubKey, data, development, nonceTrial, increment, nonceStartValue); } diff --git a/libloki/proof-of-work.js b/libloki/proof-of-work.js index 2bea4f9ef..0fbc8d28a 100644 --- a/libloki/proof-of-work.js +++ b/libloki/proof-of-work.js @@ -5,16 +5,17 @@ const DEV_NONCE_TRIALS = 10; const PROD_NONCE_TRIALS = 1000; const pow = { - // Increment Uint8Array nonce by 1 with carrying - incrementNonce(nonce) { + // Increment Uint8Array nonce by '_increment' with carrying + incrementNonce(nonce, _increment = 1) { let idx = NONCE_LEN - 1; const newNonce = new Uint8Array(nonce); - newNonce[idx] += 1; - // Nonce will just reset to 0 if all values are 255 causing infinite loop - while (newNonce[idx] === 0 && idx > 0) { + let increment = _increment; + do { + const sum = newNonce[idx] + increment; + newNonce[idx] = sum % 256; + increment = Math.floor(sum / 256); idx -= 1; - newNonce[idx] += 1; - } + } while(increment > 0 && idx >= 0); return newNonce; }, @@ -59,7 +60,7 @@ const pow = { }, // Return nonce that hashes together with payload lower than the target - async calcPoW(timestamp, ttl, pubKey, data, development = false) { + async calcPoW(timestamp, ttl, pubKey, data, development = false, _nonceTrials = null, increment = 1, startNonce = 0) { const payload = new Uint8Array( dcodeIO.ByteBuffer.wrap( timestamp.toString() + ttl.toString() + pubKey + data, @@ -67,10 +68,11 @@ const pow = { ).toArrayBuffer() ); - const nonceTrials = development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS; + const nonceTrials = _nonceTrials || (development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS); const target = pow.calcTarget(ttl, payload.length, nonceTrials); let nonce = new Uint8Array(NONCE_LEN); + nonce = pow.incrementNonce(nonce, startNonce); // initial value let trialValue = pow.bigIntToUint8Array( JSBI.BigInt(Number.MAX_SAFE_INTEGER) ); @@ -78,10 +80,13 @@ const pow = { await crypto.subtle.digest('SHA-512', payload) ); const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN); + innerPayload.set(nonce); innerPayload.set(initialHash, NONCE_LEN); let resultHash; + let nextNonce = nonce; while (pow.greaterThan(trialValue, target)) { - nonce = pow.incrementNonce(nonce); + nonce = nextNonce; + nextNonce = pow.incrementNonce(nonce, increment); innerPayload.set(nonce); // eslint-disable-next-line no-await-in-loop resultHash = await crypto.subtle.digest('SHA-512', innerPayload); diff --git a/libloki/test/metrics.js b/libloki/test/metrics.js new file mode 100644 index 000000000..cdb934eb7 --- /dev/null +++ b/libloki/test/metrics.js @@ -0,0 +1,124 @@ +/* global dcodeIO, Plotly */ +let jobId = 0; +let currentTrace = 0 +let plotlyDiv; +const workers = []; +async function run(messageLength, numWorkers = 1, nonceTrials=100) { + const timestamp = Math.floor(Date.now() / 1000); + const ttl = 4 * 24 * 60 * 60; + const pubKey = '05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802'; + const message = randomString(messageLength); + const messageBuffer = dcodeIO.ByteBuffer.wrap(message, 'utf8').toArrayBuffer(); + const data = dcodeIO.ByteBuffer.wrap(messageBuffer).toString('base64'); + const promises = []; + const t0 = performance.now(); + for (let w = 0; w < numWorkers; w += 1) { + const worker = new Worker('../../js/util_worker.js'); + workers.push(worker); + jobId += 1; + const increment = numWorkers; + const index = w; + worker.postMessage([jobId, 'calcPoW', timestamp, ttl, pubKey, data, false, nonceTrials, increment, index]); + const p = new Promise(resolve => { + worker.onmessage = (nonce) => { + resolve(nonce); + }; + }); + promises.push(p); + } + await Promise.race(promises); + const t1 = performance.now(); + const duration = (t1 - t0) / 1000; + addPoint(duration); + // clean up + workers.forEach(worker => worker.terminate()); +} + +async function runPoW({ iteration, nonceTrials, numWorkers, messageLength = 50 }) { + const name = `W:${numWorkers} - NT: ${nonceTrials} - L:${messageLength}`; + Plotly.addTraces(plotlyDiv ,{ + y: [], + type: 'box', + boxpoints: 'all', + name, + }); + for (let i = 0; i < iteration; i += 1) { + // eslint-disable-next-line no-await-in-loop + await run(messageLength, numWorkers, nonceTrials); + } + currentTrace += 1; + console.log(`done for ${name}`); +} + + +function randomString(length) { + let text = ''; + const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < length; i += 1) + text += possible.charAt(Math.floor(Math.random() * possible.length)); + return text; +} + +function addPoint(duration) { + Plotly.extendTraces(plotlyDiv, {y: [[duration]]}, [currentTrace]); +} +async function startMessageLengthRun() { + const iteration0 = parseFloat(document.getElementById('iteration0').value); + const nonceTrials0 = parseFloat(document.getElementById('nonceTrials0').value); + const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value); + const messageLengthStart0 = parseFloat(document.getElementById('messageLengthStart0').value); + const messageLengthStop0 = parseFloat(document.getElementById('messageLengthStop0').value); + const messageLengthStep0 = parseFloat(document.getElementById('messageLengthStep0').value); + for (let l = messageLengthStart0; l < messageLengthStop0; l += messageLengthStep0) { + // eslint-disable-next-line no-await-in-loop + await runPoW({ iteration: iteration0, nonceTrials: nonceTrials0, numWorkers: numWorkers0, messageLength: l }); + } +} +async function startNumWorkerRun() { + const iteration1 = parseFloat(document.getElementById('iteration1').value); + const nonceTrials1 = parseFloat(document.getElementById('nonceTrials1').value); + const numWorkersStart1 = parseFloat(document.getElementById('numWorkersStart1').value); + const numWorkersEnd1 = parseFloat(document.getElementById('numWorkersEnd1').value); + const messageLength1 = parseFloat(document.getElementById('messageLength1').value); + for (let numWorkers = numWorkersStart1; numWorkers <= numWorkersEnd1; numWorkers +=1) { + // eslint-disable-next-line no-await-in-loop + await runPoW({ iteration: iteration1, nonceTrials: nonceTrials1, numWorkers, messageLength: messageLength1 }); + } +} +async function startNonceTrialsRun() { + const iteration2 = parseFloat(document.getElementById('iteration2').value); + const messageLength2 = parseFloat(document.getElementById('messageLength2').value); + const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value); + const nonceTrialsStart2 = parseFloat(document.getElementById('nonceTrialsStart2').value); + const nonceTrialsStop2 = parseFloat(document.getElementById('nonceTrialsStop2').value); + const nonceTrialsStep2 = parseFloat(document.getElementById('nonceTrialsStep2').value); + for (let n = nonceTrialsStart2; n < nonceTrialsStop2; n += nonceTrialsStep2) { + // eslint-disable-next-line no-await-in-loop + await runPoW({ iteration: iteration2, nonceTrials: n, numWorkers: numWorkers2, messageLength: messageLength2 }); + } +} +async function start(index) { + const data = []; + const layout = {}; + const options = { + responsive: true, + }; + plotlyDiv =`plotly${index}`; + currentTrace = 0; + window.chart = Plotly.newPlot(plotlyDiv, data, layout, options); + workers.forEach(worker => worker.terminate()); + + switch(index) { + case 0: + await startMessageLengthRun(); + break; + case 1: + await startNumWorkerRun(); + break; + case 2: + await startNonceTrialsRun(); + break; + default: + break; + } +} diff --git a/metrics.html b/metrics.html new file mode 100644 index 000000000..6dc9ec42c --- /dev/null +++ b/metrics.html @@ -0,0 +1,51 @@ + + + + + pow calculations + + + + +

Influence of message length

+ + + + + + +
+ +
+ +

Influence of workers

+ + + + + +
+ +
+ +

Influence of NonceTrials

+ + + + + + +
+ +
+ + + + + + + diff --git a/metrics_app.js b/metrics_app.js new file mode 100644 index 000000000..d934787f7 --- /dev/null +++ b/metrics_app.js @@ -0,0 +1,61 @@ +const http = require('http'); +const url = require('url'); +const fs = require('fs'); +const path = require('path'); +// you can pass the parameter in the command line. e.g. node static_server.js 3000 +const port = process.argv[3] || 9000; +const hostname = process.argv[2] || 'localhost'; +// maps file extention to MIME types +const mimeType = { + '.ico': 'image/x-icon', + '.html': 'text/html', + '.js': 'text/javascript', + '.json': 'application/json', + '.css': 'text/css', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.wav': 'audio/wav', + '.mp3': 'audio/mpeg', + '.svg': 'image/svg+xml', + '.pdf': 'application/pdf', + '.doc': 'application/msword', + '.eot': 'appliaction/vnd.ms-fontobject', + '.ttf': 'aplication/font-sfnt' +}; +http.createServer(function (req, res) { +// console.log(`${req.method} ${req.url}`); + // parse URL + const parsedUrl = url.parse(req.url); + // extract URL path + // Avoid https://en.wikipedia.org/wiki/Directory_traversal_attack + // e.g curl --path-as-is http://localhost:9000/../fileInDanger.txt + // by limiting the path to current directory only + const sanitizePath = path.normalize(parsedUrl.pathname).replace(/^(\.\.[\/\\])+/, ''); + let pathname = path.join(__dirname, sanitizePath); + fs.exists(pathname, function (exist) { + if(!exist) { + // if the file is not found, return 404 + res.statusCode = 404; + res.end(`File ${pathname} not found!`); + return; + } + // if is a directory, then look for index.html + if (fs.statSync(pathname).isDirectory()) { + pathname += '/index.html'; + } + // read file from file system + fs.readFile(pathname, function(err, data){ + if(err){ + res.statusCode = 500; + res.end(`Error getting the file: ${err}.`); + } else { + // based on the URL path, extract the file extention. e.g. .js, .doc, ... + const ext = path.parse(pathname).ext; + // if the file is found, set Content-type and send data + res.setHeader('Content-type', mimeType[ext] || 'text/plain' ); + res.end(data); + } + }); + }); +}).listen(parseInt(port), hostname); +console.log(`metrics running on http://${hostname}:${port}/metrics.html`); \ No newline at end of file diff --git a/package.json b/package.json index 21dfe6bd7..f4e4e55e5 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "transpile": "tsc", "clean-transpile": "rimraf ts/**/*.js ts/*.js", "open-coverage": "open coverage/lcov-report/index.html", - "styleguide": "styleguidist server" + "styleguide": "styleguidist server", + "pow-metrics": "node metrics_app.js localhost 9000" }, "dependencies": { "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#ed4f4d179ac010c6347b291cbd4c2ebe5c773741", From 8c01bea91392dbeb49172e1184d64899719afd5f Mon Sep 17 00:00:00 2001 From: sachaaaaa Date: Tue, 8 Jan 2019 13:56:15 +1100 Subject: [PATCH 2/2] variables name consistency and remove superfluous initialisation --- js/util_worker_tasks.js | 4 ++-- libloki/proof-of-work.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/js/util_worker_tasks.js b/js/util_worker_tasks.js index 4e9b873ce..a1310e706 100644 --- a/js/util_worker_tasks.js +++ b/js/util_worker_tasks.js @@ -41,6 +41,6 @@ function stringToArrayBufferBase64(string) { function arrayBufferToStringBase64(arrayBuffer) { return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); } -function calcPoW(timestamp, ttl, pubKey, data, development, nonceTrial = undefined, increment = 1, nonceStartValue = 0) { - return pow.calcPoW(timestamp, ttl, pubKey, data, development, nonceTrial, increment, nonceStartValue); +function calcPoW(timestamp, ttl, pubKey, data, development, nonceTrials = undefined, increment = 1, startNonce = 0) { + return pow.calcPoW(timestamp, ttl, pubKey, data, development, nonceTrials, increment, startNonce); } diff --git a/libloki/proof-of-work.js b/libloki/proof-of-work.js index 0fbc8d28a..074ba3889 100644 --- a/libloki/proof-of-work.js +++ b/libloki/proof-of-work.js @@ -80,7 +80,6 @@ const pow = { await crypto.subtle.digest('SHA-512', payload) ); const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN); - innerPayload.set(nonce); innerPayload.set(initialHash, NONCE_LEN); let resultHash; let nextNonce = nonce;