Commit d6ecef73fd6edc80197140774535f1adea617314
cleanup
wanderer committed on 4/24/2017, 9:51:16 AMParent: 1171580743394a34306e2a12800ea5606f7e5d67
Files changed
README.md | changed |
index.js | changed |
kernel.js | changed |
package.json | changed |
port.js | changed |
portManager.js | changed |
tests/index.js | changed |
common.js | deleted |
crypto.js | added |
tools/wabt | deleted |
README.md | ||
---|---|---|
@@ -36,4 +36,9 @@ | ||
36 | 36 | todo |
37 | 37 | |
38 | 38 | # LICENSE |
39 | 39 | [MPL-2.0](https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2)) |
40 | + | |
41 | + | |
42 | +The Kernel enforces IPC and starts the VM | |
43 | +The hypervisor start and stops kernels | |
44 | +the VM acts as a sandbox for some given code and expose and interface to the kernel |
index.js | ||
---|---|---|
@@ -1,48 +1,69 @@ | ||
1 | -const Kernel = require('./index.js') | |
1 | +const Graph = require('ipld-graph-builder') | |
2 | +const Kernel = require('./kernel.js') | |
3 | +const crypto = require('./crypto') | |
2 | 4 | |
3 | 5 | module.exports = class Hypervisor { |
4 | 6 | constructor (opts) { |
5 | 7 | this._opts = { |
6 | - state: {}, | |
7 | - imports: [], | |
8 | - hypervisor: this | |
8 | + hypervisor: this, | |
9 | + VMs: {} | |
9 | 10 | } |
10 | 11 | |
12 | + this.graph = new Graph(opts.dag) | |
13 | + this._vmInstances = new Map() | |
11 | 14 | Object.assign(this._opts, opts) |
12 | - this._runningVMs = new Map() | |
13 | 15 | } |
14 | 16 | |
15 | - set (path, value) { | |
16 | - return this._opts.graph.set(this._opts.state, path, value) | |
17 | - } | |
18 | - | |
19 | - sendMessage (portObj, message) { | |
20 | - const vm = this.getInstaceFromPort(portObj) | |
21 | - vm.queue(message) | |
22 | - } | |
23 | - | |
24 | - async getVMFromPort (port) { | |
25 | - const id = Kernel.id(port) | |
26 | - let kernel = this._vms.get(id) | |
17 | + async getInstance (port) { | |
18 | + const id = await this.generateID(port.id) | |
19 | + let kernel = this._vmInstances.get(id) | |
27 | 20 | if (!kernel) { |
28 | 21 | // load the container from the state |
29 | 22 | await this.graph.tree(port, 2) |
30 | - kernel = new Kernel() | |
23 | + | |
24 | + // create a new kernel instance | |
25 | + const opts = Object.assign({ | |
26 | + state: port.vm, | |
27 | + id: port.id | |
28 | + }, this._opts) | |
29 | + | |
30 | + kernel = new Kernel(opts) | |
31 | + await kernel.start() | |
31 | 32 | kernel.on('idle', () => { |
32 | - this._vms.delete(id) | |
33 | + this._vmInstances.delete(id) | |
33 | 34 | }) |
34 | - this._vms.set(id, kernel) | |
35 | + this._vmInstances.set(id, kernel) | |
35 | 36 | } |
37 | + return kernel | |
36 | 38 | } |
37 | 39 | |
40 | + async send (port, message) { | |
41 | + const vm = await this.getInstance(port) | |
42 | + const id = await this.generateID(port.id) | |
43 | + message._fromPort = id | |
44 | + vm.queue(message) | |
45 | + } | |
46 | + | |
38 | 47 | // given a port, wait untill its source contract has reached the threshold |
39 | 48 | // tick count |
40 | - async waitOnPort (port, ticks) { | |
41 | - let kernel = await this.getVMFromPort(port) | |
42 | - return kernel.wait(ticks) | |
49 | + async wait (port, ticks) { | |
50 | + let kernel = await this.getInstance(port) | |
51 | + await kernel.wait(ticks) | |
52 | + return kernel | |
43 | 53 | } |
44 | 54 | |
45 | - addVM (container) { | |
46 | - this._vms.type = container.type | |
55 | + async createStateRoot (port, ticks) { | |
56 | + const kernel = await this.wait(port, ticks) | |
57 | + return this.graph.flush(kernel.state) | |
47 | 58 | } |
59 | + | |
60 | + async generateID (port) { | |
61 | + let id = Buffer.concat([port.nonce, port.parent]) | |
62 | + id = await crypto.subtle.digest('SHA-256', id) | |
63 | + return new Buffer(id).toString('hex') | |
64 | + } | |
65 | + | |
66 | + addVM (type, vm) { | |
67 | + this._opts.VMs[type] = vm | |
68 | + } | |
48 | 69 | } |
kernel.js | ||
---|---|---|
@@ -1,44 +1,46 @@ | ||
1 | -const crypto = require('webcrypto-liner') | |
2 | 1 | const PriorityQueue = require('fastpriorityqueue') |
3 | 2 | const EventEmitter = require('events') |
3 | +const BN = require('bn.js') | |
4 | 4 | const PortManager = require('./portManager.js') |
5 | 5 | |
6 | -const STATES = ['idle', 'running', 'result'] | |
6 | +const VMSTATES = ['idle', 'running', 'result'] | |
7 | 7 | |
8 | 8 | module.exports = class Kernel extends EventEmitter { |
9 | - constructor (opts = {}) { | |
9 | + constructor (opts) { | |
10 | 10 | super() |
11 | - // set up the state | |
12 | - this.opts = {} | |
13 | - this._stateIndex = 0 | |
14 | - Object.assign(this.opts, opts) | |
11 | + this._opts = opts | |
12 | + this._vmStateIndex = 0 | |
15 | 13 | this.ports = new PortManager(this) |
16 | 14 | this._waitingQueue = new PriorityQueue((a, b) => { |
17 | 15 | return a.threshold > b.threshold |
18 | 16 | }) |
19 | 17 | this.on('result', this._runNextMessage) |
20 | 18 | } |
21 | 19 | |
22 | - _updateState (message) { | |
23 | - this._stateIndex++ | |
24 | - const state = STATES[this._stateIndex] | |
25 | - this._emit(state, message) | |
20 | + start () { | |
21 | + return this.ports.start() | |
26 | 22 | } |
27 | 23 | |
28 | - get state () { | |
29 | - return STATES[this._stateIndex] | |
24 | + _updateVmState (message) { | |
25 | + this._vmStateIndex++ | |
26 | + const vmState = VMSTATES[this._stateVmIndex] | |
27 | + this._emit(vmState, message) | |
30 | 28 | } |
31 | 29 | |
30 | + get vmState () { | |
31 | + return VMSTATES[this._stateVmIndex] | |
32 | + } | |
33 | + | |
32 | 34 | queue (message) { |
33 | - this.portManager.queue(message) | |
34 | - if (this.state === 'idle') { | |
35 | + this.ports.queue(message) | |
36 | + if (this.vmState === 'idle') { | |
35 | 37 | this._runNextMessage() |
36 | 38 | } |
37 | 39 | } |
38 | 40 | |
39 | 41 | _runNextMessage () { |
40 | - this.portManager.getNextMessage(this.ticks).then(message => { | |
42 | + this.ports.getNextMessage(this.ticks).then(message => { | |
41 | 43 | if (message) { |
42 | 44 | this.run(message) |
43 | 45 | } else { |
44 | 46 | this._updateState() |
@@ -52,9 +54,9 @@ | ||
52 | 54 | * to by the VM to retrive infromation from the Environment. |
53 | 55 | */ |
54 | 56 | async run (message, imports = this.imports) { |
55 | 57 | // shallow copy |
56 | - const oldState = Object.assign({}, this.state) | |
58 | + const oldState = Object.assign({}, this._opts.state) | |
57 | 59 | let result |
58 | 60 | this._updateState(message) |
59 | 61 | try { |
60 | 62 | result = await this._vm.run(message, this, imports) || {} |
@@ -66,20 +68,20 @@ | ||
66 | 68 | } |
67 | 69 | |
68 | 70 | if (result.exception) { |
69 | 71 | // revert to the old state |
70 | - clearObject(this.opts.state) | |
71 | - Object.assign(this.opts.state, oldState) | |
72 | + clearObject(this._opts.state) | |
73 | + Object.assign(this._opts.state, oldState) | |
72 | 74 | } |
73 | 75 | |
74 | - this._updateState(result) | |
76 | + this._updateVmState(result) | |
75 | 77 | return result |
76 | 78 | } |
77 | 79 | |
78 | 80 | // returns a promise that resolves once the kernel hits the threshould tick |
79 | 81 | // count |
80 | 82 | async wait (threshold) { |
81 | - if (this._state === 'idle' && threshold > this.ticks) { | |
83 | + if (this._vmState === 'idle' && threshold > this.ticks) { | |
82 | 84 | // the cotract is at idle so wait |
83 | 85 | return this.portManager.wait(threshold) |
84 | 86 | } else { |
85 | 87 | return new Promise((resolve, reject) => { |
@@ -102,29 +104,27 @@ | ||
102 | 104 | } |
103 | 105 | } |
104 | 106 | |
105 | 107 | createPort () { |
108 | + const nonce = new BN(this.nonce) | |
109 | + nonce.iaddn(1) | |
110 | + this.nonce = nonce.toArrayLike(Uint8Array) | |
106 | 111 | return { |
107 | 112 | id: { |
108 | - '/': [this.nonce++, this.id] | |
113 | + '/': { | |
114 | + nonce: this.nonce, | |
115 | + parent: this.id | |
116 | + } | |
109 | 117 | }, |
110 | 118 | link: { |
111 | 119 | '/': {} |
112 | 120 | } |
113 | 121 | } |
114 | 122 | } |
115 | 123 | |
116 | 124 | async send (port, message) { |
117 | - return this.opts.hypervisor.send(port, message) | |
125 | + return this._opts.hypervisor.send(port, message) | |
118 | 126 | } |
119 | - | |
120 | - id () { | |
121 | - return Kernel.id(this._opts, this._opts) | |
122 | - } | |
123 | - | |
124 | - static id (id) { | |
125 | - return crypto.subtle.digest('SHA-256', Buffer.concat(id.parentId, id.nonce)) | |
126 | - } | |
127 | 127 | } |
128 | 128 | |
129 | 129 | function clearObject (myObject) { |
130 | 130 | for (var member in myObject) { |
package.json | ||
---|---|---|
@@ -1,8 +1,8 @@ | ||
1 | 1 | { |
2 | - "name": "ewasm-kernel", | |
2 | + "name": "primea-hypervisor", | |
3 | 3 | "version": "0.0.0", |
4 | - "description": "This is a JS prototype of the eWASM kernal.", | |
4 | + "description": "this is a JS implemention of the primea hypervisor", | |
5 | 5 | "scripts": { |
6 | 6 | "coverage": "node --harmony ./node_modules/istanbul/lib/cli.js cover ./tests/apiTests.js", |
7 | 7 | "coveralls": "npm run coverage && coveralls <coverage/lcov.info", |
8 | 8 | "lint": "standard", |
@@ -17,40 +17,27 @@ | ||
17 | 17 | "url": "https://github.com/ewasm/ewasm-kernel/issues" |
18 | 18 | }, |
19 | 19 | "homepage": "https://github.com/ewasm/ewasm-kernel", |
20 | 20 | "keywords": [ |
21 | - "ethereum", | |
22 | - "webassembly", | |
23 | - "wasm", | |
24 | - "ewasm" | |
21 | + "primea", | |
22 | + "hypervisor", | |
23 | + "kernel" | |
25 | 24 | ], |
26 | 25 | "author": "mjbecze <mjbecze@gmail.com>", |
27 | 26 | "contributors": "Alex Beregszaszi <alex@rtfs.hu>", |
28 | 27 | "license": "MPL-2.0", |
28 | + "dependencies": { | |
29 | + "bn.js": "^4.11.6", | |
30 | + "deepcopy": "^0.6.3", | |
31 | + "fastpriorityqueue": "^0.2.4", | |
32 | + "ipld-graph-builder": "1.0.1", | |
33 | + "node-webcrypto-ossl": "^1.0.21", | |
34 | + "primea-message": "0.0.0" | |
35 | + }, | |
29 | 36 | "devDependencies": { |
30 | 37 | "coveralls": "^2.13.0", |
31 | 38 | "ipfs": "^0.23.1", |
32 | 39 | "istanbul": "^1.1.0-alpha.1", |
33 | 40 | "standard": "10.0.1", |
34 | 41 | "tape": "^4.5.1" |
35 | - }, | |
36 | - "standard": { | |
37 | - "ignore": [ | |
38 | - "/tools/" | |
39 | - ], | |
40 | - "globals": [ | |
41 | - "WebAssembly" | |
42 | - ] | |
43 | - }, | |
44 | - "dependencies": { | |
45 | - "deepcopy": "^0.6.3", | |
46 | - "ethereumjs-block": "^1.5.0", | |
47 | - "ethereumjs-tx": "^1.2.5", | |
48 | - "ethereumjs-util": "^5.1.0", | |
49 | - "file-type": "^4.2.0", | |
50 | - "fixed-bn.js": "0.0.2", | |
51 | - "ipld-graph-builder": "1.0.1", | |
52 | - "primea-message": "0.0.0", | |
53 | - "primea-wasm-container": "0.0.0", | |
54 | - "webcrypto-liner": "^0.1.20" | |
55 | 42 | } |
56 | 43 | } |
port.js | ||
---|---|---|
@@ -9,9 +9,9 @@ | ||
9 | 9 | this.ticks = message.ticks |
10 | 10 | if (this._resolve) { |
11 | 11 | return this._resolve(message) |
12 | 12 | } else { |
13 | - this.queue.push(message) | |
13 | + this._queue.push(message) | |
14 | 14 | } |
15 | 15 | } |
16 | 16 | |
17 | 17 | // this only workls for one Promise |
portManager.js | ||
---|---|---|
@@ -1,44 +1,35 @@ | ||
1 | 1 | const Port = require('./port.js') |
2 | -const common = require('./common.js') | |
3 | 2 | |
4 | 3 | module.exports = class PortManager { |
5 | - constructor (ports, kernel) { | |
4 | + constructor (kernel) { | |
6 | 5 | this.kernel = kernel |
7 | - this.ports = ports | |
6 | + this.hypervisor = kernel._opts.hypervisor | |
7 | + this.ports = kernel._opts.state.ports | |
8 | 8 | this._portMap = new Map() |
9 | - this._hasMappedPorts = false | |
10 | - this._tempQueue = [] | |
11 | - this._mapPorts(this.ports).then(ports => { | |
12 | - this._portMap = ports | |
13 | - this.queue = this._queue | |
14 | - for (const message of this._tempQueue) { | |
15 | - this.queue(message) | |
16 | - } | |
17 | - }) | |
18 | 9 | } |
19 | 10 | |
20 | - // temporaly queue message untill the ports have been mapped. Mapping the | |
21 | - // ports is async since the ports could just be merkle links | |
22 | - queue (message) { | |
23 | - this._tempQueue.push(message) | |
24 | - } | |
25 | - | |
26 | - _queue (message) { | |
27 | - this._portMap.get(message.from).push(message) | |
28 | - } | |
29 | - | |
30 | - async _mapPorts (ports) { | |
31 | - ports = Object.key(ports).map(name => { | |
32 | - const port = ports[name] | |
33 | - this.kernel.id(port).then(id => { | |
11 | + async start () { | |
12 | + // map ports to thier id's | |
13 | + let ports = Object.keys(this.ports).map(name => { | |
14 | + const port = this.ports[name] | |
15 | + this.hypervisor.generateID(port).then(id => { | |
34 | 16 | return [id, new Port(name)] |
35 | 17 | }) |
36 | 18 | }) |
19 | + | |
20 | + // create the parent port | |
21 | + ports.push(this.hypervisor.generateID(this.kernel._opts.id).then(id => { | |
22 | + return [id, new Port('parent')] | |
23 | + })) | |
37 | 24 | ports = await Promise.all(ports) |
38 | - return new Map(ports) | |
25 | + this._portMap = new Map(ports) | |
39 | 26 | } |
40 | 27 | |
28 | + queue (message) { | |
29 | + this._portMap.get(message.fromPort).queue(message) | |
30 | + } | |
31 | + | |
41 | 32 | create (name, value) { |
42 | 33 | this.ports[name] = value |
43 | 34 | } |
44 | 35 | |
@@ -51,13 +42,18 @@ | ||
51 | 42 | delete this.ports[from] |
52 | 43 | } |
53 | 44 | |
54 | 45 | async get (name) { |
55 | - const port = await name === common.PARENT ? this.graph.get(this.state.ports, name) : this.parentId | |
56 | - const id = await this.kernel.id(port) | |
46 | + const port = await this.graph.get(this.state.ports, name) | |
47 | + const id = await this.hypervisor.generateID(port) | |
57 | 48 | return this._portMap.get(id) |
58 | 49 | } |
59 | 50 | |
51 | + async getParent () { | |
52 | + const id = await this.hypervisor.generateID(this.kernel._opts.id) | |
53 | + return this._portMap.get(id) | |
54 | + } | |
55 | + | |
60 | 56 | // waits till all ports have reached a threshold tick count |
61 | 57 | async wait (threshold) { |
62 | 58 | // find the ports that have a smaller tick count then the threshold tick count |
63 | 59 | const unkownPorts = [...this._ports].filter((id, port) => { |
@@ -65,9 +61,9 @@ | ||
65 | 61 | return !message || message.ticks < threshold |
66 | 62 | }) |
67 | 63 | |
68 | 64 | const promises = unkownPorts.map(port => { |
69 | - this.hypervisor.waitOnVM(port, threshold).then(ticks => { | |
65 | + this.hypervisor.wait(port, threshold).then(ticks => { | |
70 | 66 | // update the port's tick count |
71 | 67 | port.ticks = ticks |
72 | 68 | }) |
73 | 69 | }) |
@@ -75,9 +71,9 @@ | ||
75 | 71 | } |
76 | 72 | |
77 | 73 | async getNextMessage (ticks) { |
78 | 74 | await this.wait(ticks) |
79 | - return [...this._ports].reduce(messageArbiter).shift() | |
75 | + return [...this._portMap].reduce(messageArbiter).shift() | |
80 | 76 | } |
81 | 77 | } |
82 | 78 | |
83 | 79 | // decides which message to go firts |
tests/index.js | ||
---|---|---|
@@ -1,10 +1,74 @@ | ||
1 | 1 | const tape = require('tape') |
2 | -const ipfs = require('ipfs') | |
3 | -const graph = require('ipld-graph-builder') | |
2 | +const IPFS = require('ipfs') | |
4 | 3 | const Hypervisor = require('../') |
4 | +const Message = require('primea-message') | |
5 | 5 | |
6 | -tape('base kernel tests', t => { | |
7 | - const state = {} | |
8 | - const hypervisor = new Hypervisor() | |
9 | - t.equal() | |
6 | +const node = new IPFS() | |
7 | +node.on('error', err => { | |
8 | + console.log(err) | |
10 | 9 | }) |
10 | + | |
11 | +node.on('start', () => { | |
12 | + tape.only('basic', async t => { | |
13 | + const testVM = { | |
14 | + run (message) { | |
15 | + console.log('made it!!!!') | |
16 | + } | |
17 | + } | |
18 | + | |
19 | + try { | |
20 | + const state = { | |
21 | + id: { | |
22 | + nonce: new Uint8Array([0]), | |
23 | + parent: new Uint8Array() | |
24 | + }, | |
25 | + type: 'test', | |
26 | + vm: { | |
27 | + ports: {} | |
28 | + } | |
29 | + } | |
30 | + | |
31 | + const hypervisor = new Hypervisor(node.dag) | |
32 | + hypervisor.addVM('test', testVM) | |
33 | + | |
34 | + const message = new Message() | |
35 | + await hypervisor.send(state, message) | |
36 | + | |
37 | + await hypervisor.createStateRoot(state, Infinity) | |
38 | + console.log(state) | |
39 | + | |
40 | + node.stop(() => { | |
41 | + t.end() | |
42 | + process.exit() | |
43 | + }) | |
44 | + } catch (e) { | |
45 | + console.log(e) | |
46 | + } | |
47 | + }) | |
48 | + | |
49 | + tape('messaging', t => { | |
50 | + // const state = { | |
51 | + // id: {}, | |
52 | + // ports: { | |
53 | + // first: { | |
54 | + // id: { | |
55 | + // nonce: 1, | |
56 | + // parent: 'hash' | |
57 | + // }, | |
58 | + // code: 'js code', | |
59 | + // type: 'test', | |
60 | + // ports: { | |
61 | + | |
62 | + // } | |
63 | + // } | |
64 | + // } | |
65 | + // } | |
66 | + // const message = new Message({ | |
67 | + // type: 'create', | |
68 | + // path: 'first', | |
69 | + // data: jsCode | |
70 | + // }) | |
71 | + // hypervisor.send(port, message) | |
72 | + | |
73 | + }) | |
74 | +}) |
common.js | ||
---|---|---|
@@ -1,16 +1,0 @@ | ||
1 | -const Message = require('primea-message') | |
2 | - | |
3 | -exports.PARENT = '..' | |
4 | -exports.ROOT = '/' | |
5 | -exports.getterMessage = (name, path) => { | |
6 | - const message = new Message({ | |
7 | - data: { | |
8 | - getValue: name | |
9 | - }, | |
10 | - sync: true | |
11 | - }) | |
12 | - if (path) { | |
13 | - message.to = path | |
14 | - } | |
15 | - return message | |
16 | -} |
crypto.js | ||
---|---|---|
@@ -1,0 +1,4 @@ | ||
1 | +const WebCrypto = require('node-webcrypto-ossl') | |
2 | +module.exports = new WebCrypto({ | |
3 | + directory: `${__dirname}/.webcrypto` | |
4 | +}) |
tools/wabt | ||
---|---|---|
@@ -1,1 +1,0 @@ | ||
1 | -Subproject commit 2177e7517b857d557cbf9b8477c31f96a8ed66bb |
Built with git-ssb-web