From cde4de4a150f5d2d57ece3fac9d3d37b10505aae Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Mon, 7 Nov 2022 23:10:17 +0200 Subject: [PATCH] put computePow into web workers --- qortal-ui-plugins/build-config.js | 3 + qortal-ui-plugins/package.json | 4 +- .../plugins/core/components/ChatPage.js | 69 +++++++++----- .../core/components/computePowWorker.js | 83 +++++++++++++++++ .../core/components/computePowWorkerImage.js | 93 +++++++++++++++++++ .../plugins/utils/publish-image.js | 68 +++++--------- 6 files changed, 254 insertions(+), 66 deletions(-) create mode 100644 qortal-ui-plugins/plugins/core/components/computePowWorker.js create mode 100644 qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js diff --git a/qortal-ui-plugins/build-config.js b/qortal-ui-plugins/build-config.js index 2949988b..35506107 100644 --- a/qortal-ui-plugins/build-config.js +++ b/qortal-ui-plugins/build-config.js @@ -8,6 +8,8 @@ const commonjs = require('@rollup/plugin-commonjs'); const alias = require('@rollup/plugin-alias'); const { terser } = require('rollup-plugin-terser'); const babel = require('@rollup/plugin-babel'); +const webWorkerLoader = require('rollup-plugin-web-worker-loader'); + const aliases = {}; @@ -40,6 +42,7 @@ const generateRollupConfig = (inputFile, outputFile) => { commonjs(), globals(), progress(), + webWorkerLoader(), babel.babel({ babelHelpers: 'bundled', exclude: 'node_modules/**', diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index c81ac3c8..b1694c27 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -19,6 +19,7 @@ "dependencies": { "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", + "asmcrypto.js": "2.3.2", "compressorjs": "^1.1.1", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", "localforage": "^1.10.0", @@ -60,7 +61,8 @@ "rollup": "2.79.1", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-progress": "1.1.2", - "rollup-plugin-terser": "7.0.2" + "rollup-plugin-terser": "7.0.2", + "rollup-plugin-web-worker-loader": "^1.6.1" }, "engines": { "node": ">=16.15.0" diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index d25b29f4..d5cb59c0 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -21,7 +21,10 @@ import '@material/mwc-dialog' import '@material/mwc-icon' import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js'; import { publishData } from '../../utils/publish-image.js'; +import WebWorker from 'web-worker:./computePowWorker.js'; +import WebWorkerImage from 'web-worker:./computePowWorkerImage.js'; +// hello const messagesCache = localForage.createInstance({ name: "messages-cache", }); @@ -1221,10 +1224,14 @@ class ChatPage extends LitElement { parentEpml, metaData: undefined, uploadType: 'file', - selectedAddress: this.selectedAddress + selectedAddress: this.selectedAddress, + worker: new WebWorkerImage() }) } catch (error) { console.error(error) + this.isLoading = false; + this.chatEditor.enable(); + return } @@ -1291,17 +1298,25 @@ class ChatPage extends LitElement { return } console.log({userName, identifier }) - - await publishData({ - registeredName: userName, - file : compressedFile, - service: 'IMAGE', - identifier : identifier, - parentEpml, - metaData: undefined, - uploadType: 'file', - selectedAddress: this.selectedAddress - }) + try { + await publishData({ + registeredName: userName, + file : compressedFile, + service: 'IMAGE', + identifier : identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: new WebWorkerImage() + }) + } catch (error) { + console.error(error) + this.isLoading = false; + this.chatEditor.enable(); + return + } + const messageObject = { messageText: outSideMsg.caption, images: [{ @@ -1477,23 +1492,33 @@ class ChatPage extends LitElement { }; const _computePow = async (chatBytes) => { - const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }); - const chatBytesArray = new Uint8Array(_chatBytesArray); - const chatBytesHash = new window.parent.Sha256().process(chatBytesArray).finish().result; - const hashPtr = window.parent.sbrk(32, window.parent.heap); - const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32); - hashAry.set(chatBytesHash); - const difficulty = this.balance === 0 ? 12 : 8; - const workBufferLength = 8 * 1024 * 1024; - const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap); - let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty); + const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full' + const worker = new WebWorker(); + let nonce = null + let chatBytesArray = null + await new Promise((res, rej) => { + console.log({chatBytes}) + worker.postMessage({chatBytes, path, difficulty}); + + worker.onmessage = e => { + + + worker.terminate() + chatBytesArray = e.data.chatBytesArray + nonce = e.data.nonce + res() + + } + }) let _response = await parentEpml.request('sign_chat', { nonce: this.selectedAddress.nonce, chatBytesArray: chatBytesArray, chatNonce: nonce }); + + getSendChatResponse(_response); }; diff --git a/qortal-ui-plugins/plugins/core/components/computePowWorker.js b/qortal-ui-plugins/plugins/core/components/computePowWorker.js new file mode 100644 index 00000000..96db8beb --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/computePowWorker.js @@ -0,0 +1,83 @@ +import { Sha256 } from 'asmcrypto.js' + + +function sbrk(size, heap){ + let brk = 512 * 1024 // stack top + let old = brk + brk += size + + if (brk > heap.length) + throw new Error('heap exhausted') + + return old +} + + + +self.addEventListener('message', async e => { + console.log({data: e.data}) + const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty) + postMessage(response) + +}) + + +const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) +const heap = new Uint8Array(memory.buffer) + + + +const computePow = async (chatBytes, path, difficulty) => { + + let response = null + + await new Promise((resolve, reject)=> { + + const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }); + const chatBytesArray = new Uint8Array(_chatBytesArray); + const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; + const hashPtr = sbrk(32, heap); + const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); + hashAry.set(chatBytesHash); + + + const workBufferLength = 8 * 1024 * 1024; + const workBufferPtr = sbrk(workBufferLength, heap); + + + + const importObject = { + env: { + memory: memory + }, + }; + + function loadWebAssembly(filename, imports) { + // Fetch the file and compile it + return fetch(filename) + .then(response => response.arrayBuffer()) + .then(buffer => WebAssembly.compile(buffer)) + .then(module => { + + // Create the instance. + return new WebAssembly.Instance(module, importObject); + }); +} + + +loadWebAssembly(path) + .then(wasmModule => { + response = { + nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), + chatBytesArray + } + + resolve() + + }); + + + }) + + return response +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js b/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js new file mode 100644 index 00000000..e008361b --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js @@ -0,0 +1,93 @@ +import { Sha256 } from 'asmcrypto.js' + + + +function sbrk(size, heap){ + let brk = 512 * 1024 // stack top + let old = brk + brk += size + + if (brk > heap.length) + throw new Error('heap exhausted') + + return old +} + + + + +self.addEventListener('message', async e => { + console.log({data: e.data}) + const response = await computePow(e.data.convertedBytes, e.data.path) + postMessage(response) + +}) + + +const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) +const heap = new Uint8Array(memory.buffer) + + + +const computePow = async (convertedBytes, path) => { + + + let response = null + + await new Promise((resolve, reject)=> { + + const _convertedBytesArray = Object.keys(convertedBytes).map( + function (key) { + return convertedBytes[key] + } +) +const convertedBytesArray = new Uint8Array(_convertedBytesArray) +const convertedBytesHash = new Sha256() + .process(convertedBytesArray) + .finish().result +const hashPtr = sbrk(32, heap) +const hashAry = new Uint8Array( + memory.buffer, + hashPtr, + 32 +) + +hashAry.set(convertedBytesHash) +const difficulty = 14 +const workBufferLength = 8 * 1024 * 1024 +const workBufferPtr = sbrk( + workBufferLength, + heap +) + + const importObject = { + env: { + memory: memory + }, + }; + + function loadWebAssembly(filename, imports) { + return fetch(filename) + .then(response => response.arrayBuffer()) + .then(buffer => WebAssembly.compile(buffer)) + .then(module => { + return new WebAssembly.Instance(module, importObject); + }); +} + +console.log({path}) +loadWebAssembly(path) + .then(wasmModule => { + response = { + nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), + + } + resolve() + + }); + + + }) + + return response +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/utils/publish-image.js b/qortal-ui-plugins/plugins/utils/publish-image.js index 955b284e..33579b86 100644 --- a/qortal-ui-plugins/plugins/utils/publish-image.js +++ b/qortal-ui-plugins/plugins/utils/publish-image.js @@ -14,9 +14,9 @@ export const publishData = async ({ service, identifier, parentEpml, - metaData, uploadType, selectedAddress, + worker }) => { const validateName = async (receiverName) => { let nameRes = await parentEpml.request("apiCall", { @@ -47,35 +47,23 @@ export const publishData = async ({ const convertedBytes = window.parent.Base58.decode(convertedBytesBase58) - const _convertedBytesArray = Object.keys(convertedBytes).map( - function (key) { - return convertedBytes[key] - } - ) - const convertedBytesArray = new Uint8Array(_convertedBytesArray) - const convertedBytesHash = new window.parent.Sha256() - .process(convertedBytesArray) - .finish().result - const hashPtr = window.parent.sbrk(32, window.parent.heap) - const hashAry = new Uint8Array( - window.parent.memory.buffer, - hashPtr, - 32 - ) - - hashAry.set(convertedBytesHash) - const difficulty = 14 - const workBufferLength = 8 * 1024 * 1024 - const workBufferPtr = window.parent.sbrk( - workBufferLength, - window.parent.heap - ) - let nonce = window.parent.computePow( - hashPtr, - workBufferPtr, - workBufferLength, - difficulty - ) + let nonce = null + const computPath =window.parent.location.origin + '/memory-pow/memory-pow.wasm.full' + await new Promise((res, rej) => { + + worker.postMessage({convertedBytes, path: computPath}); + + worker.onmessage = e => { + + worker.terminate() + + nonce = e.data.nonce + res() + + } + }) + + let response = await parentEpml.request("sign_arbitrary", { nonce: selectedAddress.nonce, arbitraryBytesBase58: transactionBytesBase58, @@ -131,18 +119,7 @@ export const publishData = async ({ postBody = Buffer.from(fileBuffer).toString("base64") } - // Optional metadata - - // let title = encodeURIComponent(metaData.title || "") - // let description = encodeURIComponent(metaData.description || "") - // let category = encodeURIComponent(metaData.category || "") - // let tag1 = encodeURIComponent(metaData.tag1 || "") - // let tag2 = encodeURIComponent(metaData.tag2 || "") - // let tag3 = encodeURIComponent(metaData.tag3 || "") - // let tag4 = encodeURIComponent(metaData.tag4 || "") - // let tag5 = encodeURIComponent(metaData.tag5 || "") - - // let metadataQueryString = `title=${title}&description=${description}&category=${category}&tags=${tag1}&tags=${tag2}&tags=${tag3}&tags=${tag4}&tags=${tag5}` + let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}?apiKey=${getApiKey()}` if (identifier != null && identifier.trim().length > 0) { @@ -162,5 +139,10 @@ export const publishData = async ({ } - await validate() + try { + await validate() + } catch (error) { + throw new Error(error.message) + } + }