@theqrl/hypc 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/linker.js CHANGED
@@ -30,6 +30,23 @@ function libraryHash(fullyQualifiedLibraryName, length) {
30
30
  function libraryHashPlaceholder(fullyQualifiedLibraryName) {
31
31
  return `$${libraryHash(fullyQualifiedLibraryName, HASH_PLACEHOLDER_BODY_LENGTH)}$`;
32
32
  }
33
+ /**
34
+ * Applies the compiler's address checksum to a hex address body: a letter is upper case when
35
+ * the matching nibble of SHAKE-256 (64 bytes) of the lower-case body is 8 or more.
36
+ * Mirrors `checksummedAddressBody` in hyperion's libhyputil/CommonData.cpp.
37
+ */
38
+ function checksummedAddressBody(body) {
39
+ const lower = body.toLowerCase();
40
+ const hash = (0, js_sha3_1.shake256)(lower, ADDRESS_HEX_LENGTH * 4);
41
+ return [...lower].map((c, i) => (c >= 'a' && c <= 'f' && parseInt(hash[i], 16) >= 8 ? c.toUpperCase() : c)).join('');
42
+ }
43
+ /**
44
+ * Accepts an all-lower-case or all-upper-case body, or a mixed-case body with a valid checksum,
45
+ * as the compiler does for `settings.libraries`.
46
+ */
47
+ function passesAddressChecksum(body) {
48
+ return body === body.toLowerCase() || body === body.toUpperCase() || body === checksummedAddressBody(body);
49
+ }
33
50
  /**
34
51
  * Finds all placeholders corresponding to the specified library label and replaces them
35
52
  * with a concrete address. Works with both hex-encoded and binary bytecode as long as
@@ -105,6 +122,9 @@ function linkBytecode(bytecode, libraries) {
105
122
  }
106
123
  // remove address prefix
107
124
  hexAddress = hexAddress.slice(ADDRESS_PREFIX.length);
125
+ if (!passesAddressChecksum(hexAddress)) {
126
+ throw new Error(`Invalid address checksum specified for ${libraryName}`);
127
+ }
108
128
  bytecode = replacePlaceholder(bytecode, libraryName, hexAddress);
109
129
  bytecode = replacePlaceholder(bytecode, libraryHashPlaceholder(libraryName), hexAddress);
110
130
  }
package/package.json CHANGED
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "name": "@theqrl/hypc",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Hyperion compiler",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "hypcjs": "hypc.js"
8
8
  },
9
+ "files": [
10
+ "*.js",
11
+ "bindings/*.js",
12
+ "common/*.js"
13
+ ],
9
14
  "scripts": {
10
15
  "build": "tsc",
11
16
  "postbuild": "node build/postbuild.js && cp README.md LICENSE dist/",
12
- "lint": "eslint --ext .js,.ts .",
13
- "lint:fix": "eslint --fix --ext .js,.ts .",
17
+ "lint": "eslint .",
18
+ "lint:fix": "eslint --fix .",
14
19
  "updateBinary": "node build/clean.js && ts-node ./downloadCurrentVersion.ts && ts-node ./verifyVersion.ts",
15
20
  "prepack": "node build/pack-publish-block.js",
16
21
  "build:tarball": "npm run updateBinary && npm run build && BYPASS_SAFETY_CHECK=true npm pack ./dist",
@@ -19,8 +24,7 @@
19
24
  "copyTestFiles": "cp -r ./test/resources ./dist/test/",
20
25
  "pretest": "npm run lint && npm run build && npm run copyTestFiles",
21
26
  "test": "cd dist && tape ./test/index.js --no-remote-versions",
22
- "coverage": "nyc npm run test",
23
- "coveralls": "npm run coverage && coveralls <coverage/lcov.info"
27
+ "coverage": "nyc npm run test"
24
28
  },
25
29
  "repository": {
26
30
  "type": "git",
@@ -37,33 +41,29 @@
37
41
  "url": "https://github.com/theQRL/hypc-js/issues"
38
42
  },
39
43
  "homepage": "https://github.com/theQRL/hypc-js#readme",
44
+ "engines": {
45
+ "node": ">=22.12.0"
46
+ },
40
47
  "dependencies": {
41
48
  "command-exists": "^1.2.8",
42
- "commander": "^8.1.0",
43
- "follow-redirects": "^1.12.1",
44
- "js-sha3": "0.8.0",
45
- "memorystream": "^0.3.1",
46
- "semver": "^5.5.0",
47
- "tmp": "0.0.33"
49
+ "commander": "^15.0.0",
50
+ "follow-redirects": "^1.16.2",
51
+ "js-sha3": "0.13.0",
52
+ "semver": "^7.8.5",
53
+ "tmp": "0.2.7"
48
54
  },
49
55
  "devDependencies": {
50
- "@types/node": "^16.11.7",
51
- "@types/semver": "^7.3.9",
52
- "@types/tape": "^4.13.2",
56
+ "@types/node": "^22.12.0",
57
+ "@types/semver": "^7.8.0",
58
+ "@types/tape": "^5.8.1",
53
59
  "@types/tmp": "^0.2.3",
54
- "@typescript-eslint/eslint-plugin": "^5.8.0",
55
- "@typescript-eslint/parser": "^5.8.0",
56
- "coveralls": "^3.0.0",
57
- "eslint": "^7.32.0",
58
- "eslint-config-standard": "^16.0.3",
59
- "eslint-plugin-import": "^2.25.3",
60
- "eslint-plugin-node": "^11.1.0",
61
- "eslint-plugin-promise": "^5.1.1",
62
- "nyc": "^15.1.0",
63
- "tape": "^4.11.0",
60
+ "eslint": "^10.13.0",
61
+ "neostandard": "^0.14.1",
62
+ "nyc": "^18.0.0",
63
+ "tape": "^5.10.3",
64
64
  "tape-spawn": "^1.4.2",
65
65
  "ts-node": "^10.4.0",
66
- "typescript": "^4.5.4"
66
+ "typescript": "~6.0.2"
67
67
  },
68
68
  "nyc": {
69
69
  "exclude": [
package/smtchecker.js CHANGED
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
+ const helpers_1 = require("./common/helpers");
2
3
  // This function checks the standard JSON output for auxiliaryInputRequested,
3
4
  // where smtlib2queries represent the queries created by the SMTChecker.
4
5
  // The function runs an SMT solver on each query and adjusts the input for
@@ -29,7 +30,8 @@ function smtCallback(solverFunction, solver) {
29
30
  return { contents: result };
30
31
  }
31
32
  catch (err) {
32
- return { error: err };
33
+ // The compiler takes the error as a string, like the contents.
34
+ return { error: (0, helpers_1.describeException)(err) };
33
35
  }
34
36
  };
35
37
  }
package/smtsolver.js CHANGED
@@ -15,34 +15,51 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  const command_exists_1 = require("command-exists");
26
36
  const child_process_1 = require("child_process");
27
37
  const fs = __importStar(require("fs"));
28
38
  const tmp = __importStar(require("tmp"));
29
39
  // Timeout in ms.
30
40
  const timeout = 10000;
41
+ // Solver answers are short; this only bounds a runaway solver.
42
+ const maxBuffer = 64 * 1024 * 1024;
31
43
  const potentialSolvers = [
32
44
  {
33
45
  name: 'z3',
34
46
  command: 'z3',
35
- params: '-smt2 rlimit=20000000 rewriter.pull_cheap_ite=true fp.spacer.q3.use_qgen=true fp.spacer.mbqi=false fp.spacer.ground_pobs=false'
47
+ params: ['-smt2', 'rlimit=20000000', 'rewriter.pull_cheap_ite=true', 'fp.spacer.q3.use_qgen=true', 'fp.spacer.mbqi=false', 'fp.spacer.ground_pobs=false']
36
48
  },
37
49
  {
38
50
  name: 'Eldarica',
39
51
  command: 'eld',
40
- params: '-horn -t:' + (timeout / 1000) // Eldarica takes timeout in seconds.
52
+ params: ['-horn', '-t:' + (timeout / 1000)] // Eldarica takes timeout in seconds.
53
+ },
54
+ {
55
+ name: 'cvc5',
56
+ command: 'cvc5',
57
+ params: ['--lang=smt2', '--tlimit=' + timeout]
41
58
  },
42
59
  {
43
60
  name: 'cvc4',
44
61
  command: 'cvc4',
45
- params: '--lang=smt2 --tlimit=' + timeout
62
+ params: ['--lang=smt2', '--tlimit=' + timeout]
46
63
  }
47
64
  ];
48
65
  const solvers = potentialSolvers.filter(solver => (0, command_exists_1.sync)(solver.command));
@@ -59,18 +76,26 @@ function solve(query, solver) {
59
76
  fs.writeFileSync(tmpFile.name, query);
60
77
  let solverOutput;
61
78
  try {
62
- solverOutput = (0, child_process_1.execSync)(solver.command + ' ' + solver.params + ' ' + tmpFile.name, {
79
+ // No shell: the solver gets its arguments as an array. A caller's solver configuration may
80
+ // still give params as one string, as before; it is split on whitespace.
81
+ const params = typeof solver.params === 'string' ? solver.params.split(/\s+/).filter(Boolean) : solver.params;
82
+ solverOutput = (0, child_process_1.execFileSync)(solver.command, [...params, tmpFile.name], {
63
83
  encoding: 'utf8',
64
- maxBuffer: 1024 * 1024 * 1024,
84
+ maxBuffer,
65
85
  stdio: 'pipe',
66
- timeout: timeout // Enforce timeout on the process, since solvers can sometimes go around it.
86
+ timeout, // Enforce timeout on the process, since solvers can sometimes go around it.
87
+ killSignal: 'SIGKILL' // A solver can ignore SIGTERM.
67
88
  }).toString();
68
89
  }
69
90
  catch (e) {
70
- // execSync throws if the process times out or returns != 0.
91
+ // The output was cut off at maxBuffer, so even an answer at its start cannot be trusted.
92
+ if (e.code === 'ENOBUFS') {
93
+ throw new Error(`Failed to solve SMT query. The solver wrote more than ${maxBuffer} bytes.`);
94
+ }
95
+ // execFileSync throws if the process times out or returns != 0.
71
96
  // The latter might happen with z3 if the query asks for a model
72
97
  // for an UNSAT formula. We can still use stdout.
73
- solverOutput = e.stdout.toString();
98
+ solverOutput = (e.stdout || '').toString();
74
99
  if (!solverOutput.startsWith('sat') &&
75
100
  !solverOutput.startsWith('unsat') &&
76
101
  !solverOutput.startsWith('unknown') &&
@@ -79,8 +104,10 @@ function solve(query, solver) {
79
104
  throw new Error('Failed to solve SMT query. ' + e.toString());
80
105
  }
81
106
  }
82
- // Trigger early manual cleanup
83
- tmpFile.removeCallback();
107
+ finally {
108
+ // Trigger early manual cleanup
109
+ tmpFile.removeCallback();
110
+ }
84
111
  return solverOutput;
85
112
  }
86
113
  module.exports = {
package/translate.js CHANGED
@@ -42,7 +42,7 @@ function translateErrors(ret, errors) {
42
42
  type = 'Error';
43
43
  }
44
44
  ret.push({
45
- type: type,
45
+ type,
46
46
  component: 'general',
47
47
  severity: (type === 'Warning') ? 'warning' : 'error',
48
48
  message: errors[error],
package/verifyVersion.js CHANGED
@@ -16,13 +16,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
16
16
  }) : function(o, v) {
17
17
  o["default"] = v;
18
18
  });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
26
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
27
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
38
  };
package/wrapper.js CHANGED
@@ -1,20 +1,161 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
- const memorystream_1 = __importDefault(require("memorystream"));
38
+ const crypto_1 = require("crypto");
6
39
  const follow_redirects_1 = require("follow-redirects");
40
+ const fs = __importStar(require("fs"));
41
+ const js_sha3_1 = require("js-sha3");
42
+ const path = __importStar(require("path"));
7
43
  const formatters_1 = require("./formatters");
8
44
  const helpers_1 = require("./common/helpers");
9
45
  const bindings_1 = __importDefault(require("./bindings"));
10
46
  const translate_1 = __importDefault(require("./translate"));
11
47
  const Module = module.constructor;
12
- function wrapper(hypjson) {
13
- const { coreBindings, compileBindings, methodFlags } = (0, bindings_1.default)(hypjson);
48
+ /**
49
+ * Wraps an Emscripten compiler module in the hypc-js API.
50
+ *
51
+ * A JavaScript exception that unwinds through the compiler's wasm frames, such as the RangeError
52
+ * of a stack overflow inside the compiler, leaves that module instance unable to compile (see
53
+ * `guarded` in bindings/compile.ts). `compile` throws the exception, and the next compilation
54
+ * runs on a fresh instance of the module, which `reload` returns. By default a module that
55
+ * `require` loaded is reloaded from its file, and `loadRemoteVersion` reloads from the downloaded
56
+ * source. Where neither applies, as in a browser or a bundle, pass a function that returns a fresh
57
+ * module. Without one, the same instance goes on once the compiler has discarded the abandoned
58
+ * compilation, which compilers from 0.3.0 on do in hyperion_reset(); with an older compiler, or
59
+ * after a WebAssembly trap, the next compilation returns an error that says the module cannot be
60
+ * used again.
61
+ *
62
+ * @param hypjson The Emscripten compiled Hyperion object.
63
+ * @param reload Returns a fresh instance of the same module.
64
+ */
65
+ function wrapper(hypjson, reload = reloaderFor(hypjson)) {
66
+ let bindings = (0, bindings_1.default)(hypjson);
67
+ const { methodFlags } = bindings;
68
+ // Why no compilation can run any more, once that is certain: the instance cannot compile, and no
69
+ // fresh one can be loaded.
70
+ let unusable;
71
+ // Set while the instance goes on after discarding an abandoned compilation and its next output
72
+ // has not yet shown whether the compiler supports that; holds why it was needed.
73
+ let recoveringInPlace;
74
+ const withoutReplacement = (why) => `The compiler module cannot be used again: ${why}. Load a fresh module, or pass a function that does so as the second argument of setupMethods.`;
75
+ // The bindings of the current module instance, after an exception unwound through the previous
76
+ // one: of a fresh instance where one can be loaded, otherwise of the same instance after asking
77
+ // it to discard the abandoned compilation. Throws when neither is possible.
78
+ function usableBindings() {
79
+ if (unusable !== undefined) {
80
+ throw new Error(unusable);
81
+ }
82
+ if (!bindings.poisoned) {
83
+ return bindings;
84
+ }
85
+ const why = `an exception unwound through the compiler during an earlier compilation (${(0, helpers_1.describeException)(bindings.poisonedBy)})`;
86
+ if (reload !== undefined) {
87
+ try {
88
+ bindings = (0, bindings_1.default)(reload());
89
+ }
90
+ catch (e) {
91
+ throw new Error(`The compiler module cannot be used again: ${why}, and reloading it failed: ${(0, helpers_1.describeException)(e)}`);
92
+ }
93
+ return bindings;
94
+ }
95
+ // No fresh instance can be loaded here, as in a browser or a bundle. A JavaScript exception
96
+ // leaves the instance's memory intact, and hyperion_reset() of compilers from 0.3.0 on
97
+ // discards the abandoned compilation's state, so the same instance can go on; whether this
98
+ // compiler does that shows in its next output (see compileMethod). What the abandoned
99
+ // compilation allocated stays allocated, since its destructors never ran and cannot be run
100
+ // now, which is why a fresh instance is preferred wherever one can be loaded. After a trap (a
101
+ // WebAssembly.RuntimeError, which is also what an Emscripten abort throws) the memory cannot
102
+ // be trusted.
103
+ if (typeof WebAssembly !== 'undefined' && bindings.poisonedBy instanceof WebAssembly.RuntimeError) {
104
+ unusable = withoutReplacement(`${why}, and the instance cannot be reused after a WebAssembly trap`);
105
+ throw new Error(unusable);
106
+ }
107
+ if ((0, helpers_1.isNil)(bindings.coreBindings.reset)) {
108
+ unusable = withoutReplacement(`${why}, and this compiler has no hyperion_reset() to discard it with`);
109
+ throw new Error(unusable);
110
+ }
111
+ try {
112
+ bindings.coreBindings.reset();
113
+ }
114
+ catch (e) {
115
+ unusable = withoutReplacement(`${why}, and hyperion_reset() failed afterwards: ${(0, helpers_1.describeException)(e)}`);
116
+ throw new Error(unusable);
117
+ }
118
+ bindings.poisoned = false;
119
+ bindings.poisonedBy = undefined;
120
+ recoveringInPlace = why;
121
+ return bindings;
122
+ }
123
+ // The compile method of that name, run on the current module instance, or null if the compiler
124
+ // lacks the method.
125
+ function compileMethod(name) {
126
+ if ((0, helpers_1.isNil)(bindings.compileBindings[name])) {
127
+ return null;
128
+ }
129
+ return function (...args) {
130
+ let current;
131
+ try {
132
+ current = usableBindings();
133
+ }
134
+ catch (e) {
135
+ return (0, formatters_1.formatFatalError)(e.message);
136
+ }
137
+ const output = current.compileBindings[name](...args);
138
+ if (recoveringInPlace !== undefined) {
139
+ const why = recoveringInPlace;
140
+ recoveringInPlace = undefined;
141
+ if (reportsStaleCompilerStack(output)) {
142
+ unusable = withoutReplacement(`${why}, and this compiler's hyperion_reset() does not discard the abandoned compilation (compilers from 0.3.0 on do)`);
143
+ return (0, formatters_1.formatFatalError)(unusable);
144
+ }
145
+ }
146
+ return output;
147
+ };
148
+ }
149
+ const compileBindings = {
150
+ compileJson: compileMethod('compileJson'),
151
+ compileJsonCallback: compileMethod('compileJsonCallback'),
152
+ compileJsonMulti: compileMethod('compileJsonMulti'),
153
+ compileStandard: compileMethod('compileStandard')
154
+ };
14
155
  return {
15
- version: coreBindings.version,
16
- semver: coreBindings.versionToSemver,
17
- license: coreBindings.license,
156
+ version: () => bindings.coreBindings.version(),
157
+ semver: () => bindings.coreBindings.versionToSemver(),
158
+ license: () => bindings.coreBindings.license(),
18
159
  lowlevel: {
19
160
  compileSingle: compileBindings.compileJson,
20
161
  compileMulti: compileBindings.compileJsonMulti,
@@ -28,36 +169,170 @@ function wrapper(hypjson) {
28
169
  nativeStandardJSON: methodFlags.compileJsonStandardSupported
29
170
  },
30
171
  compile: compileStandardWrapper.bind(this, compileBindings),
31
- // Loads the compiler of the given version from the github repository
32
- // instead of from the local filesystem.
172
+ // Loads the compiler of the given version from binaries.theqrl.org instead of from the
173
+ // local filesystem, after checking it against list.json.
33
174
  loadRemoteVersion,
34
175
  // Use this if you want to add wrapper functions around the pure module.
35
176
  setupMethods: wrapper
36
177
  };
37
178
  }
38
- function loadRemoteVersion(versionString, callback) {
39
- const memoryStream = new memorystream_1.default(null, { readable: false });
40
- const url = `https://binaries.theqrl.org/bin/hypjson-${versionString}.js`;
41
- follow_redirects_1.https.get(url, response => {
179
+ // Loads the source of a compiler module as require() does, in a Module of its own that is in
180
+ // neither require.cache nor module.children, so that an instance can be dropped once it is no
181
+ // longer usable. Emscripten's Node support adds a process-wide `unhandledRejection` listener on
182
+ // every load, which would accumulate and keep every instance alive; the listeners a load adds are
183
+ // removed again.
184
+ function loadModuleSource(source, filename) {
185
+ const events = ['uncaughtException', 'unhandledRejection'];
186
+ const listenersBefore = events.map(event => process.listeners(event));
187
+ try {
188
+ const hypjson = new Module(filename);
189
+ hypjson.filename = filename;
190
+ hypjson.paths = Module._nodeModulePaths(path.dirname(filename));
191
+ hypjson._compile(source, filename);
192
+ hypjson.loaded = true;
193
+ return hypjson.exports;
194
+ }
195
+ finally {
196
+ events.forEach((event, i) => {
197
+ for (const listener of process.listeners(event)) {
198
+ if (!listenersBefore[i].includes(listener)) {
199
+ process.removeListener(event, listener);
200
+ }
201
+ }
202
+ });
203
+ }
204
+ }
205
+ // A function that loads a fresh instance of a module that require() loaded, found through
206
+ // require.cache; undefined where there is no such record, as in a browser or a bundle.
207
+ function reloaderFor(hypjson) {
208
+ const cache = typeof require === 'function' ? require.cache : undefined;
209
+ if (!cache) {
210
+ return undefined;
211
+ }
212
+ const record = Object.keys(cache)
213
+ .map(key => cache[key])
214
+ .find(entry => entry && entry.exports === hypjson && typeof entry.filename === 'string');
215
+ if (!record) {
216
+ return undefined;
217
+ }
218
+ return () => loadModuleSource(fs.readFileSync(record.filename, 'utf8'), record.filename);
219
+ }
220
+ // True if the compiler's output reports the CompilerStack of an abandoned compilation, which is
221
+ // what an instance whose hyperion_reset() did not discard it answers to every compilation.
222
+ function reportsStaleCompilerStack(output) {
223
+ let parsed;
224
+ try {
225
+ parsed = JSON.parse(output);
226
+ }
227
+ catch (e) {
228
+ return false;
229
+ }
230
+ const errors = parsed && parsed.errors;
231
+ return Array.isArray(errors) && errors.some(error => {
232
+ // Compilers before 0.4.x reported errors as strings.
233
+ const message = typeof error === 'string' ? error : error && error.message;
234
+ const internal = typeof error === 'string' || (error && error.type === 'InternalCompilerError');
235
+ return internal && typeof message === 'string' && message.includes('You shall not have another CompilerStack aside me.');
236
+ });
237
+ }
238
+ const BINARIES_HOST = 'binaries.theqrl.org';
239
+ const BINARIES_URL = `https://${BINARIES_HOST}/bin/`;
240
+ // list.json is a few kilobytes and a compiler build about 9 MB.
241
+ const MAX_LIST_BYTES = 1024 * 1024;
242
+ const MAX_BUILD_BYTES = 64 * 1024 * 1024;
243
+ // Follow redirects only to https URLs on the binaries host.
244
+ function beforeRedirect(options) {
245
+ if (options.protocol !== 'https:' || options.hostname !== BINARIES_HOST) {
246
+ throw new Error(`Refusing to follow a redirect to ${options.protocol}//${options.hostname}.`);
247
+ }
248
+ }
249
+ // Calls back once, with an error or the complete body of a 200 response.
250
+ function download(url, maxBytes, callback) {
251
+ let done = false;
252
+ const finish = (error, body) => {
253
+ if (!done) {
254
+ done = true;
255
+ callback(error, body);
256
+ }
257
+ };
258
+ follow_redirects_1.https.get(url, { beforeRedirect, maxRedirects: 5 }, response => {
42
259
  if (response.statusCode !== 200) {
43
- callback(new Error(`Error retrieving binary: ${response.statusMessage}`));
260
+ response.resume();
261
+ finish(new Error(`Error retrieving ${url}: ${response.statusCode} ${response.statusMessage}`));
262
+ return;
44
263
  }
45
- else {
46
- response.pipe(memoryStream);
47
- response.on('end', () => {
48
- // Based on the require-from-string package.
49
- const hypjson = new Module();
50
- hypjson._compile(memoryStream.toString(), `hypjson-${versionString}.js`);
51
- if (module.parent && module.parent.children) {
52
- // Make sure the module is plugged into the hierarchy correctly to have parent
53
- // properly garbage collected.
54
- module.parent.children.splice(module.parent.children.indexOf(hypjson), 1);
55
- }
56
- callback(null, wrapper(hypjson.exports));
57
- });
264
+ const chunks = [];
265
+ let length = 0;
266
+ response.on('data', chunk => {
267
+ length += chunk.length;
268
+ if (length > maxBytes) {
269
+ response.destroy();
270
+ finish(new Error(`${url} is larger than ${maxBytes} bytes.`));
271
+ }
272
+ else {
273
+ chunks.push(chunk);
274
+ }
275
+ });
276
+ response.on('end', () => finish(null, Buffer.concat(chunks)));
277
+ response.on('error', finish);
278
+ }).on('error', finish);
279
+ }
280
+ // The list.json entry for "latest", a release such as "0.2.0", or a build such as
281
+ // "v0.2.0+commit.e36a0aea".
282
+ function findBuild(list, versionString) {
283
+ const releases = list.releases || {};
284
+ const buildPath = versionString === 'latest'
285
+ ? releases[list.latestRelease]
286
+ : releases[versionString] || `hypjson-${versionString}.js`;
287
+ const build = (list.builds || []).find(entry => entry.path === buildPath);
288
+ if (!build ||
289
+ !/^hypjson-v[0-9A-Za-z.+-]+\.js$/.test(build.path) ||
290
+ typeof build.keccak256 !== 'string' ||
291
+ typeof build.sha256 !== 'string') {
292
+ throw new Error(`Version ${versionString} is not in ${BINARIES_URL}list.json.`);
293
+ }
294
+ return build;
295
+ }
296
+ // Downloads a compiler build and runs it only if its keccak256 and sha256 match list.json.
297
+ function loadRemoteVersion(versionString, callback) {
298
+ download(BINARIES_URL + 'list.json', MAX_LIST_BYTES, (listError, listBody) => {
299
+ if (listError) {
300
+ callback(listError);
301
+ return;
302
+ }
303
+ let build;
304
+ try {
305
+ build = findBuild(JSON.parse(listBody.toString('utf8')), versionString);
306
+ }
307
+ catch (e) {
308
+ callback(e);
309
+ return;
58
310
  }
59
- }).on('error', function (error) {
60
- callback(error);
311
+ download(BINARIES_URL + build.path, MAX_BUILD_BYTES, (error, body) => {
312
+ if (error) {
313
+ callback(error);
314
+ return;
315
+ }
316
+ const keccak = '0x' + (0, js_sha3_1.keccak256)(body);
317
+ const sha256 = '0x' + (0, crypto_1.createHash)('sha256').update(body).digest('hex');
318
+ if (keccak !== build.keccak256.toLowerCase() || sha256 !== build.sha256.toLowerCase()) {
319
+ callback(new Error(`${build.path} does not match the keccak256 and sha256 in list.json, so it was not run.`));
320
+ return;
321
+ }
322
+ let hypc;
323
+ try {
324
+ // The source stays referenced so that a fresh instance can be loaded if an exception
325
+ // unwinds through this one (see wrapper).
326
+ const source = body.toString('utf8');
327
+ const load = () => loadModuleSource(source, build.path);
328
+ hypc = wrapper(load(), load);
329
+ }
330
+ catch (e) {
331
+ callback(e);
332
+ return;
333
+ }
334
+ callback(null, hypc);
335
+ });
61
336
  });
62
337
  }
63
338
  // Expects a Standard JSON I/O but supports old compilers
@@ -87,12 +362,12 @@ function compileStandardWrapper(compile, inputRaw, readCallback) {
87
362
  }
88
363
  // Try to wrap around old versions
89
364
  if (!(0, helpers_1.isNil)(compile.compileJsonCallback)) {
90
- const inputJson = JSON.stringify({ sources: sources });
365
+ const inputJson = JSON.stringify({ sources });
91
366
  const output = compile.compileJsonCallback(inputJson, optimize, readCallback);
92
367
  return translateOutput(output, libraries);
93
368
  }
94
369
  if (!(0, helpers_1.isNil)(compile.compileJsonMulti)) {
95
- const output = compile.compileJsonMulti(JSON.stringify({ sources: sources }), optimize);
370
+ const output = compile.compileJsonMulti(JSON.stringify({ sources }), optimize);
96
371
  return translateOutput(output, libraries);
97
372
  }
98
373
  // Try our luck with an ancient compiler