Better handling of Solidity compliation (#4860)

* Better use of SW

* Safe-guard against pending SW register bug (in Chrome)

* Added a simple Worker for Solidity compilation
This commit is contained in:
Nicolas Gotchac 2017-03-11 15:25:45 +01:00 committed by Gav Wood
parent 1c37ea5860
commit e73d867dab
7 changed files with 159 additions and 161 deletions

View File

@ -201,6 +201,7 @@
"scryptsy": "2.0.0", "scryptsy": "2.0.0",
"solc": "ngotchac/solc-js", "solc": "ngotchac/solc-js",
"store": "1.3.20", "store": "1.3.20",
"sw-toolbox": "^3.6.0",
"u2f-api": "0.0.9", "u2f-api": "0.0.9",
"u2f-api-polyfill": "0.4.3", "u2f-api-polyfill": "0.4.3",
"uglify-js": "2.8.2", "uglify-js": "2.8.2",
@ -210,6 +211,7 @@
"validator": "6.2.0", "validator": "6.2.0",
"web3": "0.17.0-beta", "web3": "0.17.0-beta",
"whatwg-fetch": "2.0.1", "whatwg-fetch": "2.0.1",
"worker-loader": "^0.8.0",
"zxcvbn": "4.4.1" "zxcvbn": "4.4.1"
} }
} }

View File

@ -294,7 +294,7 @@ contract multisig {
// TODO: document // TODO: document
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
function confirm(bytes32 _h) external returns (bool o_success); function confirm(bytes32 _h) returns (bool o_success);
} }
// usage: // usage:

View File

@ -16,15 +16,22 @@
import PromiseWorker from 'promise-worker'; import PromiseWorker from 'promise-worker';
import runtime from 'serviceworker-webpack-plugin/lib/runtime'; import runtime from 'serviceworker-webpack-plugin/lib/runtime';
import WebWorker from 'worker-loader!~/webWorker.js';
import { setWorker } from './workerActions'; import { setWorker } from './workerActions';
function getWorker () { // Setup the Service Worker
// Setup the Service Worker setupServiceWorker()
if ('serviceWorker' in navigator) { .then(() => console.log('SW is setup'))
return runtime .catch((error) => console.error('SW error', error));
.register()
.then(() => navigator.serviceWorker.ready) function setupServiceWorker () {
if (!('serviceWorker' in navigator)) {
return Promise.reject('Service Worker is not available in your browser.');
}
const getServiceWorker = () => {
return navigator.serviceWorker.ready
.then((registration) => { .then((registration) => {
const worker = registration.active; const worker = registration.active;
@ -32,9 +39,34 @@ function getWorker () {
return new PromiseWorker(worker); return new PromiseWorker(worker);
}); });
} };
return Promise.reject('Service Worker is not available in your browser.'); return new Promise((resolve, reject) => {
// Safe guard for registration bugs (happens in Chrome sometimes)
const timeoutId = window.setTimeout(() => {
console.warn('could not register SW after 2.5s');
getServiceWorker().then(resolve).catch(reject);
}, 2500);
// Setup the Service Worker
runtime
.register()
.then(() => {
window.clearTimeout(timeoutId);
return getServiceWorker();
})
.then(resolve).catch(reject);
});
}
function getWorker () {
try {
const worker = new PromiseWorker(new WebWorker());
return Promise.resolve(worker);
} catch (error) {
return Promise.reject(error);
}
} }
export const setupWorker = (store) => { export const setupWorker = (store) => {
@ -43,27 +75,16 @@ export const setupWorker = (store) => {
const state = getState(); const state = getState();
const stateWorker = state.worker.worker; const stateWorker = state.worker.worker;
if (stateWorker !== undefined && !(stateWorker && stateWorker._worker.state === 'redundant')) { if (stateWorker !== undefined) {
return; return;
} }
getWorker() getWorker()
.then((worker) => { .then((worker) => {
if (worker) {
worker._worker.addEventListener('statechange', (event) => {
console.warn('worker state changed to', worker._worker.state);
// Re-install the new Worker
if (worker._worker.state === 'redundant') {
setupWorker(store);
}
});
}
dispatch(setWorker(worker)); dispatch(setWorker(worker));
}) })
.catch((error) => { .catch((error) => {
console.error('sw', error); console.error('setupWorker', error);
dispatch(setWorker(null)); dispatch(setWorker(null));
}); });
}; };

View File

@ -14,15 +14,15 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import registerPromiseWorker from 'promise-worker/register'; import toolbox from 'sw-toolbox';
import { Signer } from '~/util/signer';
import SolidityUtils from '~/util/solidity';
const CACHE_NAME = 'parity-cache-v1'; toolbox.precache(self.serviceWorkerOption.assets);
registerPromiseWorker((msg) => { /**
return handleMessage(msg); * Cache the SOLC files : if not available, make a network request
}); */
toolbox.router.any(/raw.githubusercontent.com\/ethereum\/solc-bin(.+)list\.json$/, toolbox.cacheFirst);
toolbox.router.any(/raw.githubusercontent.com\/ethereum\/solc-bin(.+)soljson(.+)\.js$/, toolbox.cacheFirst);
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
event.waitUntil(self.skipWaiting()); event.waitUntil(self.skipWaiting());
@ -31,126 +31,3 @@ self.addEventListener('install', (event) => {
self.addEventListener('activate', (event) => { self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim()); event.waitUntil(self.clients.claim());
}); });
self.addEventListener('fetch', (event) => {
const { url } = event.request;
if (/raw.githubusercontent.com\/ethereum\/solc-bin(.+)list\.json$/.test(url)) {
// Return the cached version, but still update it in background
return event.respondWith(cachedFetcher(event.request, true));
}
if (/raw.githubusercontent.com\/ethereum\/solc-bin(.+)soljson(.+)\.js$/.test(url)) {
return event.respondWith(cachedFetcher(event.request));
}
});
self.solc = {};
self.files = {};
function cachedFetcher (request, update = false) {
return caches
.match(request)
.then((response) => {
// Return cached response if exists and no
// updates needed
if (response && !update) {
return response;
}
const fetcher = fetch(request.clone())
.then((response) => {
// Check if we received a valid response
if (!response || response.status !== 200) {
return response;
}
return caches
.open(CACHE_NAME)
.then((cache) => {
cache.put(request, response.clone());
return response;
});
});
// Cache hit - return response
// Still want to perform the fetch (update)
if (response) {
return response;
}
return fetcher;
});
}
function handleMessage (message) {
switch (message.action) {
case 'compile':
return compile(message.data);
case 'load':
return getCompiler(message.data).then(() => 'ok');
case 'setFiles':
return setFiles(message.data);
case 'getSignerSeed':
return getSignerSeed(message.data);
default:
console.warn(`unknown action "${message.action}"`);
return null;
}
}
function getSignerSeed (data) {
console.log('deriving seed from service-worker');
const { wallet, password } = data;
return Signer.getSeed(wallet, password);
}
function compile (data) {
const { build } = data;
return getCompiler(build)
.then((compiler) => {
return SolidityUtils.compile(data, compiler);
});
}
function setFiles (files) {
const prevFiles = self.files;
const nextFiles = files.reduce((obj, file) => {
obj[file.name] = file.sourcecode;
return obj;
}, {});
self.files = {
...prevFiles,
...nextFiles
};
return 'ok';
}
function getCompiler (build) {
const { longVersion } = build;
const fetcher = (url) => {
const request = new Request(url);
return cachedFetcher(request);
};
if (!self.solc[longVersion]) {
self.solc[longVersion] = SolidityUtils
.getCompiler(build, fetcher)
.then((compiler) => {
self.solc[longVersion] = compiler;
return compiler;
});
}
return Promise.resolve(self.solc[longVersion]);
}

View File

@ -50,18 +50,24 @@ export default class SolidityUtils {
return compiled; return compiled;
} }
static getCompiler (build, _fetcher) { static getCompiler (build) {
const { longVersion, path } = build; const { longVersion, path } = build;
const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`; const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
const fetcher = typeof _fetcher === 'function'
? _fetcher
: (url) => fetch(url);
const isWorker = typeof window !== 'object'; const isWorker = typeof window !== 'object';
return fetcher(URL) if (isWorker) {
return new Promise((resolve, reject) => {
self.importScripts(URL);
setTimeout(() => {
const compiler = solc(self.Module);
return resolve(compiler);
}, 50);
});
}
return fetch(URL)
.then((r) => r.text()) .then((r) => r.text())
.then((code) => { .then((code) => {
// `window` for main thread, `self` for workers // `window` for main thread, `self` for workers

View File

@ -105,7 +105,7 @@ class WriteContract extends Component {
className={ styles.editor } className={ styles.editor }
style={ { flex: `${size}%` } } style={ { flex: `${size}%` } }
> >
<h2>{ this.renderTitle() }</h2> <h2>asd{ this.renderTitle() }</h2>
<Editor <Editor
ref='editor' ref='editor'

92
js/src/webWorker.js Normal file
View File

@ -0,0 +1,92 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import registerPromiseWorker from 'promise-worker/register';
import { Signer } from '~/util/signer';
import SolidityUtils from '~/util/solidity';
registerPromiseWorker((msg) => {
return handleMessage(msg);
});
self.solc = {};
self.files = {};
function handleMessage (message) {
switch (message.action) {
case 'compile':
return compile(message.data);
case 'load':
return getCompiler(message.data).then(() => 'ok');
case 'setFiles':
return setFiles(message.data);
case 'getSignerSeed':
return getSignerSeed(message.data);
default:
console.warn(`unknown action "${message.action}"`);
return null;
}
}
function getSignerSeed (data) {
console.log('deriving seed from service-worker');
const { wallet, password } = data;
return Signer.getSeed(wallet, password);
}
function compile (data) {
const { build } = data;
return getCompiler(build)
.then((compiler) => {
return SolidityUtils.compile(data, compiler);
});
}
function setFiles (files) {
const prevFiles = self.files;
const nextFiles = files.reduce((obj, file) => {
obj[file.name] = file.sourcecode;
return obj;
}, {});
self.files = {
...prevFiles,
...nextFiles
};
return 'ok';
}
function getCompiler (build) {
const { longVersion } = build;
if (!self.solc[longVersion]) {
self.solc[longVersion] = SolidityUtils
.getCompiler(build)
.then((compiler) => {
self.solc[longVersion] = compiler;
return compiler;
});
}
return Promise.resolve(self.solc[longVersion]);
}