Commit 97de846fb83df689a7ca6fed41f902c652d2a33e
Merge pull request #113 from primea/non-atomic
Non atomicwanderer authored on 4/28/2017, 6:01:58 PM
GitHub committed on 4/28/2017, 6:01:58 PM
Parent: 9224a38130d9646acdb70e89eadcd3de8b626082
Parent: eacc6aefd1d880bdcfc1c451260d67651c742adb
Files changed
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,111 +1,87 @@ | ||
1 | -const EventEmitter = require('events') | |
2 | -const PortManager = require('./portManager.js') | |
3 | -const codeHandler = require('./codeHandler.js') | |
4 | -const AtomicMessage = require('primea-message/atomic') | |
1 | +const Graph = require('ipld-graph-builder') | |
2 | +const multibase = require('multibase') | |
3 | +const Kernel = require('./kernel.js') | |
5 | 4 | |
6 | -module.exports = class Kernel extends EventEmitter { | |
7 | - constructor (opts = {}) { | |
8 | - super() | |
9 | - // set up the state | |
10 | - this.graph = opts.graph | |
11 | - this.path = opts.path || '' | |
12 | - this.imports = opts.imports | |
13 | - const state = this.state = opts.state || {} | |
5 | +module.exports = class Hypervisor { | |
6 | + constructor (opts) { | |
7 | + this._opts = { | |
8 | + VMs: {} | |
9 | + } | |
14 | 10 | |
15 | - // set up the vm | |
16 | - this._vm = (opts.codeHandler || codeHandler).init(opts.code || state) | |
17 | - this._vmstate = 'idle' | |
11 | + this.graph = new Graph(opts.dag) | |
12 | + delete opts.dag | |
13 | + this._vmInstances = new Map() | |
14 | + Object.assign(this._opts, opts) | |
15 | + } | |
18 | 16 | |
19 | - // set up ports | |
20 | - this.ports = new PortManager({ | |
21 | - state: state, | |
22 | - graph: this.graph, | |
23 | - parentPort: opts.parentPort, | |
24 | - Kernel: Kernel, | |
25 | - imports: this.imports, | |
26 | - path: this.path | |
27 | - }) | |
17 | + async getInstance (port) { | |
18 | + let id = await this.generateID(port) | |
19 | + let kernel = this._vmInstances.get(id) | |
20 | + if (!kernel) { | |
21 | + // load the container from the state | |
22 | + await this.graph.tree(port, 2) | |
28 | 23 | |
29 | - this.ports.on('message', index => { | |
30 | - this.runNextMessage(index) | |
31 | - }) | |
24 | + // create a new kernel instance | |
25 | + const VM = this._opts.VMs[port.type] | |
26 | + | |
27 | + kernel = new Kernel({ | |
28 | + parentPort: port, | |
29 | + hypervisor: this, | |
30 | + VM: VM | |
31 | + }) | |
32 | + | |
33 | + await kernel.start() | |
34 | + kernel.on('idle', () => { | |
35 | + this._vmInstances.delete(id) | |
36 | + }) | |
37 | + this._vmInstances.set(id, kernel) | |
38 | + } | |
39 | + return kernel | |
32 | 40 | } |
33 | 41 | |
34 | - runNextMessage (index = 0) { | |
35 | - // load the next message from port space | |
36 | - return this.ports.peek(index).then(message => { | |
37 | - if (message && | |
38 | - (this._vmstate === 'idle' || | |
39 | - (AtomicMessage.isAtomic(message) && message.isCyclic(this)))) { | |
40 | - this._currentMessage = message | |
41 | - this.ports.remove(index) | |
42 | - return this.run(message) | |
43 | - } else { | |
44 | - this._vmstate = 'idle' | |
45 | - this.emit('idle') | |
46 | - } | |
47 | - }) | |
42 | + async send (port, message) { | |
43 | + const vm = await this.getInstance(port) | |
44 | + const id = await this.generateID(port) | |
45 | + message._fromPort = id | |
46 | + vm.queue(message) | |
48 | 47 | } |
49 | 48 | |
50 | - /** | |
51 | - * run the kernels code with a given enviroment | |
52 | - * The Kernel Stores all of its state in the Environment. The Interface is used | |
53 | - * to by the VM to retrive infromation from the Environment. | |
54 | - */ | |
55 | - async run (message, imports = this.imports) { | |
56 | - const self = this | |
57 | - function revert (oldState) { | |
58 | - // revert the state | |
59 | - clearObject(self.state) | |
60 | - Object.assign(self.state, oldState) | |
61 | - } | |
49 | + // given a port, wait untill its source contract has reached the threshold | |
50 | + // tick count | |
51 | + async wait (port, threshold) { | |
52 | + let kernel = await this.getInstance(port) | |
53 | + return kernel.wait(threshold) | |
54 | + } | |
62 | 55 | |
63 | - // shallow copy | |
64 | - const oldState = Object.assign({}, this.state) | |
65 | - let result | |
66 | - this._vmstate = 'running' | |
67 | - try { | |
68 | - result = await this._vm.run(message, this, imports) || {} | |
69 | - } catch (e) { | |
70 | - result = { | |
71 | - exception: true, | |
72 | - exceptionError: e | |
56 | + createPort (type, id = {nonce: [0], parent: null}) { | |
57 | + const VM = this._opts.VMs[type] | |
58 | + return { | |
59 | + 'messages': [], | |
60 | + 'id': { | |
61 | + '/': id | |
62 | + }, | |
63 | + 'type': type, | |
64 | + 'link': { | |
65 | + '/': VM.createState() | |
73 | 66 | } |
74 | 67 | } |
68 | + } | |
75 | 69 | |
76 | - // if we trapped revert all the sent messages | |
77 | - if (result.exception) { | |
78 | - // revert to the old state | |
79 | - revert(oldState) | |
80 | - message._reject(result) | |
81 | - } else if (AtomicMessage.isAtomic(message) && !message.hasResponded) { | |
82 | - message.respond(result) | |
83 | - } | |
84 | - | |
85 | - message._committed().then(() => { | |
86 | - this.runNextMessage(0) | |
87 | - }).catch((e) => { | |
88 | - revert(oldState) | |
89 | - }) | |
90 | - return result | |
70 | + async createStateRoot (port, ticks) { | |
71 | + await this.wait(port, ticks) | |
72 | + return this.graph.flush(port) | |
91 | 73 | } |
92 | 74 | |
93 | - async send (portName, message) { | |
94 | - if (AtomicMessage.isAtomic(message)) { | |
95 | - // record that this message has traveled thourgh this kernel. This is used | |
96 | - // to detect re-entry | |
97 | - message._visited(this, this._currentMessage) | |
75 | + async generateID (port) { | |
76 | + let id = await this.graph.flush(port.id) | |
77 | + id = id['/'] | |
78 | + if (Buffer.isBuffer(id)) { | |
79 | + id = multibase.encode('base58btc', id).toString() | |
98 | 80 | } |
99 | - return this.ports.send(portName, message) | |
81 | + return id | |
100 | 82 | } |
101 | 83 | |
102 | - shutdown () { | |
103 | - this.ports.close() | |
84 | + addVM (type, vm) { | |
85 | + this._opts.VMs[type] = vm | |
104 | 86 | } |
105 | 87 | } |
106 | - | |
107 | -function clearObject (myObject) { | |
108 | - for (var member in myObject) { | |
109 | - delete myObject[member] | |
110 | - } | |
111 | -} |
package.json | ||
---|---|---|
@@ -1,13 +1,13 @@ | ||
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 | - "coverage": "node --harmony ./node_modules/istanbul/lib/cli.js cover ./tests/apiTests.js", | |
6 | + "coverage": "node ./node_modules/istanbul/lib/cli.js cover ./tests/index.js", | |
7 | 7 | "coveralls": "npm run coverage && coveralls <coverage/lcov.info", |
8 | 8 | "lint": "standard", |
9 | - "test": "node --harmony --expose-wasm ./tests/interfaceRunner.js", | |
9 | + "test": "node ./tests/index.js", | |
10 | 10 | "build": "node ./tests/buildTests.js && ./tools/wabt/out/wast2wasm ./wasm/interface.wast -o ./wasm/interface.wasm" |
11 | 11 | }, |
12 | 12 | "repository": { |
13 | 13 | "type": "git", |
@@ -17,38 +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 | + "multibase": "^0.3.4", | |
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 | - "fixed-bn.js": "0.0.2", | |
50 | - "ipld-graph-builder": "1.0.1", | |
51 | - "primea-message": "0.0.0", | |
52 | - "primea-wasm-container": "0.0.0" | |
53 | 42 | } |
54 | 43 | } |
port.js | ||
---|---|---|
@@ -1,26 +1,38 @@ | ||
1 | -const EventEmitter = require('events') | |
2 | - | |
3 | -module.exports = class Port extends EventEmitter { | |
1 | +module.exports = class Port { | |
4 | 2 | constructor (name) { |
5 | - super() | |
6 | 3 | this.name = name |
7 | - this.connected = false | |
4 | + this.hasSent = false | |
5 | + this._queue = [] | |
6 | + this.ticks = 0 | |
8 | 7 | } |
9 | 8 | |
10 | - connect (destPort) { | |
11 | - if (!this.connected) { | |
12 | - this.destPort = destPort | |
13 | - destPort.destPort = this | |
14 | - this.connected = true | |
9 | + queue (message) { | |
10 | + this.ticks = message._ticks | |
11 | + if (this._resolve) { | |
12 | + return this._resolve(message) | |
13 | + } else { | |
14 | + this._queue.push(message) | |
15 | 15 | } |
16 | 16 | } |
17 | 17 | |
18 | - async send (message) { | |
19 | - message._hops++ | |
20 | - this.destPort.recieve(message) | |
18 | + // this only workls for one Promise | |
19 | + nextMessage () { | |
20 | + const message = this.queue.shift() | |
21 | + | |
22 | + return new Promise((resolve, reject) => { | |
23 | + if (message) { | |
24 | + resolve(message) | |
25 | + } else { | |
26 | + this._resolve = resolve | |
27 | + } | |
28 | + }) | |
21 | 29 | } |
22 | 30 | |
23 | - recieve (message) { | |
24 | - this.emit('message', message) | |
31 | + peek () { | |
32 | + return this._queue[0] | |
25 | 33 | } |
34 | + | |
35 | + shift () { | |
36 | + return this._queue.shift() | |
37 | + } | |
26 | 38 | } |
portManager.js | ||
---|---|---|
@@ -1,80 +1,94 @@ | ||
1 | -const EventEmitter = require('events') | |
2 | -const path = require('path') | |
3 | -const AtomicMessage = require('primea-message/atomic') | |
4 | 1 | const Port = require('./port.js') |
5 | -const common = require('./common.js') | |
2 | +const PARENT = Symbol('parent') | |
6 | 3 | |
7 | -module.exports = class PortManager extends EventEmitter { | |
8 | - constructor (opts) { | |
9 | - super() | |
10 | - Object.assign(this, opts) | |
11 | - this._queue = [] | |
12 | - // set up the parent port | |
13 | - const parentPort = new Port(common.PARENT) | |
14 | - parentPort.on('message', message => { | |
15 | - this._recieveMessage(message) | |
16 | - }) | |
4 | +// decides which message to go firts | |
5 | +function messageArbiter (pairA, pairB) { | |
6 | + const a = pairA[1].peek() | |
7 | + const b = pairB[1].peek() | |
17 | 8 | |
18 | - // create the cache | |
19 | - this.cache = new Map() | |
20 | - this.cache.set(common.PARENT, parentPort) | |
9 | + if (!a) { | |
10 | + return pairB | |
11 | + } else if (!b) { | |
12 | + return pairA | |
21 | 13 | } |
22 | 14 | |
23 | - _recieveMessage (message) { | |
24 | - const index = this._queue.push(message) - 1 | |
25 | - this.emit('message', index) | |
15 | + const aGasPrice = a.resources.gasPrice | |
16 | + const bGasPrice = b.resources.gasPrice | |
17 | + if (a.ticks !== b.ticks) { | |
18 | + return a.ticks < b.ticks ? pairA : pairB | |
19 | + } else if (aGasPrice === bGasPrice) { | |
20 | + return a.hash() > b.hash() ? pairA : pairB | |
21 | + } else { | |
22 | + return aGasPrice > bGasPrice ? pairA : pairB | |
26 | 23 | } |
24 | +} | |
27 | 25 | |
28 | - async get (name) { | |
29 | - let port = this.cache.get(name) | |
30 | - if (!port) { | |
31 | - port = new Port(name) | |
32 | - port.on('message', message => { | |
33 | - this._recieveMessage(message) | |
34 | - }) | |
35 | - // create destination kernel | |
36 | - const state = await this.graph.get(this.state, name) | |
26 | +module.exports = class PortManager { | |
27 | + constructor (kernel) { | |
28 | + this.kernel = kernel | |
29 | + this.hypervisor = kernel._opts.hypervisor | |
30 | + this.ports = kernel.state.ports | |
31 | + this.parentPort = kernel._opts.parentPort | |
32 | + this._portMap = new Map() | |
33 | + } | |
37 | 34 | |
38 | - const destKernel = new this.Kernel({ | |
39 | - state: state, | |
40 | - graph: this.graph, | |
41 | - parentPort: port, | |
42 | - imports: this.imports, | |
43 | - path: path.join(this.path, name) | |
44 | - }) | |
35 | + async start () { | |
36 | + // map ports to thier id's | |
37 | + let ports = Object.keys(this.ports).map(name => { | |
38 | + const port = this.ports[name] | |
39 | + this._mapPort(name, port) | |
40 | + }) | |
45 | 41 | |
46 | - // shutdown the kernel when it is done doing it work | |
47 | - destKernel.on('idle', () => { | |
48 | - destKernel.shutdown() | |
49 | - this.cache.delete(name) | |
50 | - }) | |
42 | + // create the parent port | |
43 | + await Promise.all(ports) | |
44 | + const parentID = await this.hypervisor.generateID(this.kernel._opts.parentPort) | |
45 | + this._portMap.set(parentID, new Port(PARENT)) | |
46 | + } | |
51 | 47 | |
52 | - // find and connect the destination port | |
53 | - const destPort = await destKernel.ports.get(common.PARENT) | |
54 | - port.connect(destPort) | |
55 | - this.cache.set(name, port) | |
56 | - } | |
57 | - return port | |
48 | + async _mapPort (name, port) { | |
49 | + const id = await this.hypervisor.generateID(port) | |
50 | + port = new Port(name) | |
51 | + this._portMap.set(id, port) | |
58 | 52 | } |
59 | 53 | |
60 | - async peek (index = 0) { | |
61 | - return this._queue[index] | |
54 | + queue (message) { | |
55 | + this._portMap.get(message.fromPort).queue(message) | |
62 | 56 | } |
63 | 57 | |
64 | - remove (index) { | |
65 | - return this._queue.splice(index, index + 1) | |
58 | + set (name, port) { | |
59 | + this.ports[name] = port | |
60 | + return this._mapPort(name, port) | |
66 | 61 | } |
67 | 62 | |
68 | - async send (portName, message) { | |
69 | - const port = await this.get(portName) | |
70 | - port.send(message) | |
71 | - return AtomicMessage.isAtomic(message) ? message.result() : {} | |
63 | + async get (port) { | |
64 | + const id = await this.hypervisor.generateID(port) | |
65 | + return this._portMap.get(id) | |
72 | 66 | } |
73 | 67 | |
74 | - close () { | |
75 | - for (let port in this.cache) { | |
76 | - port.emit('close') | |
68 | + getRef (key) { | |
69 | + return this.ports[key] | |
70 | + } | |
71 | + | |
72 | + // waits till all ports have reached a threshold tick count | |
73 | + async wait (threshold) { | |
74 | + // find the ports that have a smaller tick count then the threshold tick count | |
75 | + const unkownPorts = [...this._portMap].filter(([id, port]) => { | |
76 | + return (port.hasSent || port.name === PARENT) && port.ticks < threshold | |
77 | + }) | |
78 | + | |
79 | + const promises = unkownPorts.map(async ([id, port]) => { | |
80 | + const portObj = port.name === PARENT ? this.parentPort : this.ports[port.name] | |
81 | + // update the port's tick count | |
82 | + port.ticks = await this.hypervisor.wait(portObj, threshold) | |
83 | + }) | |
84 | + return Promise.all(promises) | |
85 | + } | |
86 | + | |
87 | + async getNextMessage (ticks) { | |
88 | + await this.wait(ticks) | |
89 | + const portMap = [...this._portMap].reduce(messageArbiter) | |
90 | + if (portMap) { | |
91 | + return portMap[1].shift() | |
77 | 92 | } |
78 | - this.cache.clear() | |
79 | 93 | } |
80 | 94 | } |
tests/apiTests.js | ||
---|---|---|
@@ -1,68 +1,0 @@ | ||
1 | -const tape = require('tape') | |
2 | -const Hypervisor = require('../hypervisor.js') | |
3 | -const Message = require('primea-message/atomic') | |
4 | -const Graph = require('ipld-graph-builder') | |
5 | -const IPFS = require('ipfs') | |
6 | - | |
7 | -const ipfs = new IPFS() | |
8 | -const graph = new Graph(ipfs) | |
9 | - | |
10 | -ipfs.on('start', async () => { | |
11 | - tape('send and reciving messages', async t => { | |
12 | - const root = {} | |
13 | - try { | |
14 | - const hypervisor = new Hypervisor(graph, root) | |
15 | - const path = 'two/three' | |
16 | - await hypervisor.set(path, { | |
17 | - code: message => { | |
18 | - t.pass('got message') | |
19 | - t.end() | |
20 | - return {} | |
21 | - } | |
22 | - }) | |
23 | - hypervisor.send('one', new Message({ | |
24 | - to: path | |
25 | - })) | |
26 | - } catch (e) { | |
27 | - console.log(e) | |
28 | - } | |
29 | - }) | |
30 | - | |
31 | - tape('reverts', async t => { | |
32 | - try { | |
33 | - const root = {} | |
34 | - const hypervisor = new Hypervisor(graph, root) | |
35 | - const path = 'one/two/three' | |
36 | - const path2 = 'one/two/three/four' | |
37 | - await hypervisor.set(path, { | |
38 | - code: async (message, kernel) => { | |
39 | - console.log('here!!') | |
40 | - await kernel.send('four', new Message()) | |
41 | - throw new Error('vm exception') | |
42 | - } | |
43 | - }) | |
44 | - | |
45 | - await hypervisor.set(path2, { | |
46 | - code: (message, kernel) => { | |
47 | - kernel.graph.set(kernel.state, 'something', { | |
48 | - somevalue: 'value' | |
49 | - }) | |
50 | - return 'done!' | |
51 | - } | |
52 | - }) | |
53 | - | |
54 | - const message = new Message({ | |
55 | - to: path.split('/').slice(1) | |
56 | - }) | |
57 | - hypervisor.send(path.split('/')[0], message) | |
58 | - const result = await message.result() | |
59 | - t.equals(result.exception, true) | |
60 | - const expectedRoot = '{"one":{"two":{"three":{"/":{"four":{"/":{}}}}}}}' | |
61 | - t.equals(JSON.stringify(root), expectedRoot, 'should produce correct root') | |
62 | - } catch (e) { | |
63 | - console.log(e) | |
64 | - } | |
65 | - t.end() | |
66 | - process.exit() | |
67 | - }) | |
68 | -}) |
tests/index.js | ||
---|---|---|
@@ -1,0 +1,107 @@ | ||
1 | +const tape = require('tape') | |
2 | +const IPFS = require('ipfs') | |
3 | +const Hypervisor = require('../') | |
4 | +const Message = require('primea-message') | |
5 | + | |
6 | +const node = new IPFS() | |
7 | + | |
8 | +class BaseContainer { | |
9 | + constructor (kernel) { | |
10 | + this.kernel = kernel | |
11 | + } | |
12 | + | |
13 | + static createState (code) { | |
14 | + return { | |
15 | + nonce: [0], | |
16 | + ports: {} | |
17 | + } | |
18 | + } | |
19 | +} | |
20 | + | |
21 | +node.on('error', err => { | |
22 | + console.log(err) | |
23 | +}) | |
24 | + | |
25 | +node.on('start', () => { | |
26 | + tape('basic', async t => { | |
27 | + const message = new Message() | |
28 | + const expectedState = { | |
29 | + '/': 'zdpuB3eZQJuXMnQrdiF5seMvx3zC2xT1EqrQScoPcTs8ESxYx' | |
30 | + } | |
31 | + | |
32 | + class testVMContainer extends BaseContainer { | |
33 | + run (m) { | |
34 | + t.true(m === message, 'should recive a message') | |
35 | + } | |
36 | + } | |
37 | + | |
38 | + const hypervisor = new Hypervisor({dag: node.dag}) | |
39 | + hypervisor.addVM('test', testVMContainer) | |
40 | + const port = hypervisor.createPort('test') | |
41 | + | |
42 | + await hypervisor.send(port, message) | |
43 | + await hypervisor.createStateRoot(port, Infinity) | |
44 | + | |
45 | + t.deepEquals(port, expectedState, 'expected') | |
46 | + t.end() | |
47 | + }) | |
48 | + | |
49 | + tape('one child contract', async t => { | |
50 | + let message = new Message() | |
51 | + const expectedState = { '/': 'zdpuAqtY43BMaTCB5nTt7kooeKAWibqGs44Uwy9jJQHjTnHRK' } | |
52 | + let hasResolved = false | |
53 | + | |
54 | + class testVMContainer2 extends BaseContainer { | |
55 | + run (m) { | |
56 | + t.true(m === message, 'should recive a message 2') | |
57 | + return new Promise((resolve, reject) => { | |
58 | + setTimeout(() => { | |
59 | + this.kernel.incrementTicks(1) | |
60 | + hasResolved = true | |
61 | + resolve() | |
62 | + }, 200) | |
63 | + }) | |
64 | + } | |
65 | + } | |
66 | + | |
67 | + class testVMContainer extends BaseContainer { | |
68 | + async run (m) { | |
69 | + const port = await this.kernel.createPort(this.kernel.ports, 'test2', 'child') | |
70 | + await this.kernel.send(port, m) | |
71 | + this.kernel.incrementTicks(1) | |
72 | + } | |
73 | + } | |
74 | + | |
75 | + const hypervisor = new Hypervisor({dag: node.dag}) | |
76 | + hypervisor.addVM('test', testVMContainer) | |
77 | + hypervisor.addVM('test2', testVMContainer2) | |
78 | + const port = hypervisor.createPort('test') | |
79 | + | |
80 | + await hypervisor.send(port, message) | |
81 | + await hypervisor.createStateRoot(port, Infinity) | |
82 | + t.true(hasResolved, 'should resolve before generating the state root') | |
83 | + t.deepEquals(port, expectedState, 'expected state') | |
84 | + | |
85 | + // test reviving the state | |
86 | + class testVMContainer3 extends BaseContainer { | |
87 | + async run (m) { | |
88 | + const port = this.kernel.getPort(this.kernel.ports, 'child') | |
89 | + await this.kernel.send(port, m) | |
90 | + this.kernel.incrementTicks(1) | |
91 | + } | |
92 | + } | |
93 | + | |
94 | + hypervisor.addVM('test', testVMContainer3) | |
95 | + | |
96 | + // revive ports | |
97 | + message = new Message() | |
98 | + await hypervisor.graph.tree(expectedState, 1) | |
99 | + await hypervisor.send(expectedState['/'], message) | |
100 | + await hypervisor.createStateRoot(expectedState['/'], Infinity) | |
101 | + | |
102 | + t.end() | |
103 | + node.stop(() => { | |
104 | + process.exit() | |
105 | + }) | |
106 | + }) | |
107 | +}) |
tests/buildTests.js | ||
---|---|---|
@@ -1,15 +1,0 @@ | ||
1 | -const fs = require('fs') | |
2 | -const cp = require('child_process') | |
3 | -const path = require('path') | |
4 | - | |
5 | -const dir = path.join(__dirname, '/interface') | |
6 | -// get the test names | |
7 | -let tests = fs.readdirSync(dir).filter((file) => file.endsWith('.wast')) | |
8 | -// tests = ['balance.wast'] | |
9 | -// run the tests | |
10 | -for (let testName of tests) { | |
11 | - console.log(testName) | |
12 | - testName = testName.split('.')[0] | |
13 | - // Compile Command | |
14 | - cp.execSync(`${__dirname}/../tools/wabt/out/wast2wasm ${dir}/${testName}.wast -o ${dir}/${testName}.wasm`) | |
15 | -} |
tests/interface/address.json | ||
---|---|---|
@@ -1,14 +1,0 @@ | ||
1 | -{ | |
2 | - "callValue": "0x00", | |
3 | - "callData": "0x00", | |
4 | - "state": { | |
5 | - "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | - "balance": "0x056bc75e2d63100000", | |
7 | - "code": "0x00", | |
8 | - "nonce": "0x00" | |
9 | - } | |
10 | - }, | |
11 | - "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | - "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
13 | - "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" | |
14 | -} |
tests/interface/address.wasm | ||
---|---|---|
@@ -1,3 +1,0 @@ | ||
1 | - asm ` ` ethereum | |
2 | -getAddress main memory | |
3 | -! @A A ) Bݐ������� Q@ |
tests/interface/address.wast | ||
---|---|---|
@@ -1,18 +1,0 @@ | ||
1 | -;; starts with an address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b | |
2 | -(module | |
3 | - (import "ethereum" "getAddress" (func $address (param i32))) | |
4 | - | |
5 | - (memory 1) | |
6 | - (export "main" (func 0)) | |
7 | - (export "memory" (memory 0)) | |
8 | - (func | |
9 | - (block | |
10 | - ;; loads the address into memory | |
11 | - (call $address (i32.const 0)) | |
12 | - (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x72a1048901c1485d)) | |
13 | - (return) | |
14 | - ) | |
15 | - (unreachable) | |
16 | - ) | |
17 | - ) | |
18 | -) |
tests/interface/balance.json | ||
---|---|---|
@@ -1,14 +1,0 @@ | ||
1 | -{ | |
2 | - "callValue": "0x00", | |
3 | - "callData": "0x00", | |
4 | - "state": { | |
5 | - "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | - "balance": "0x056bc75e2d63100000", | |
7 | - "code": "0x00", | |
8 | - "nonce": "0x00" | |
9 | - } | |
10 | - }, | |
11 | - "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | - "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
13 | - "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" | |
14 | -} |
tests/interface/balance.wasm | ||
---|---|---|
@@ -1,5 +1,0 @@ | ||
1 | - asm | |
2 | -` ` ethereum | |
3 | -getBalance p memory main callback A | |
4 | -' | |
5 | - A A A @A ) B��������Q@ A ]H���r�h)���oJ-�{ |
tests/interface/balance.wast | ||
---|---|---|
@@ -1,30 +1,0 @@ | ||
1 | -;; address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b has a balance of 0x056bc75e2d63100000 (100 ETH) | |
2 | -(module | |
3 | - (import "ethereum" "getBalance" (func $balance (param i32 i32 i32))) | |
4 | - (memory 1 ) | |
5 | - (data (i32.const 0) "\5d\48\c1\01\89\04\a1\72\88\68\29\bb\bd\9c\6f\4a\2d\06\c4\7b") | |
6 | - (export "memory" (memory 0)) | |
7 | - (export "main" (func $main)) | |
8 | - | |
9 | - (table | |
10 | - (export "callback") | |
11 | - anyfunc | |
12 | - (elem | |
13 | - $callback | |
14 | - ) | |
15 | - ) | |
16 | - | |
17 | - (func $main | |
18 | - (call $balance (i32.const 0) (i32.const 0) (i32.const 0)) | |
19 | - ) | |
20 | - | |
21 | - (func $callback | |
22 | - (block | |
23 | - (if (i64.eq (i64.load (i32.const 0)) (i64.const 0x0500000000000000)) | |
24 | - (return) | |
25 | - ) | |
26 | - (unreachable) | |
27 | - ) | |
28 | - ) | |
29 | -) | |
30 | - |
tests/interface/basic_gas_ops.json | ||
---|---|---|
@@ -1,15 +1,0 @@ | ||
1 | -{ | |
2 | - "callValue": "0x00", | |
3 | - "callData": "0x00", | |
4 | - "state": { | |
5 | - "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | - "balance": "0x056bc75e2d63100000", | |
7 | - "code": "0x00", | |
8 | - "nonce": "0x00" | |
9 | - } | |
10 | - }, | |
11 | - "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | - "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
13 | - "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
14 | - "gasLeft": 1000 | |
15 | -} |
tests/interface/basic_gas_ops.wasm | ||
---|---|---|
@@ -1,3 +1,0 @@ | ||
1 | - asm `~ ` ~` )ethereumuseGas ethereum | |
2 | -getGasLeft main | |
3 | - @B B�Q@ |
tests/interface/basic_gas_ops.wast | ||
---|---|---|
@@ -1,24 +1,0 @@ | ||
1 | -;; starts with 1000 gas | |
2 | -(module | |
3 | - (import "ethereum" "useGas" (func $useGas (param i64))) | |
4 | - (import "ethereum" "getGasLeft" (func $gas (result i64))) | |
5 | - | |
6 | - (export "main" (func $main)) | |
7 | - (func $main | |
8 | - ;; test adding gas | |
9 | - (block | |
10 | - (call $useGas (i64.const 1)) | |
11 | - (if (i64.eq (call $gas) (i64.const 997)) | |
12 | - (return) | |
13 | - ) | |
14 | - (unreachable) | |
15 | - ) | |
16 | - (block | |
17 | - (call $useGas (i64.const 1)) | |
18 | - (if (i64.eq (call $gas) (i64.const 996)) | |
19 | - (return) | |
20 | - ) | |
21 | - (unreachable) | |
22 | - ) | |
23 | - ) | |
24 | -) |
tests/interface/call.json | ||
---|---|---|
@@ -1,14 +1,0 @@ | ||
1 | -{ | |
2 | - "callValue": "0x00", | |
3 | - "callData": "0x00", | |
4 | - "state": { | |
5 | - "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | - "balance": "0x056bc75e2d63100000", | |
7 | - "code": "0x00", | |
8 | - "nonce": "0x00" | |
9 | - } | |
10 | - }, | |
11 | - "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | - "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
13 | - "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" | |
14 | -} |
tests/interface/call.wasm | ||
---|---|---|
@@ -1,2 +1,0 @@ | ||
1 | - asm `~` ` ethereumcall p memory main callback A | |
2 | -:+ @A A6 A4A����6 B�A AA4AA8AA A F@ |
tests/interface/call.wast | ||
---|---|---|
@@ -1,36 +1,0 @@ | ||
1 | -;; starts with an address of 5d48c1018904a172886829bbbd9c6f4a2d06c47b | |
2 | -(module | |
3 | - (import "ethereum" "call" (func $call (param i64 i32 i32 i32 i32 i32 i32 i32) (result i32))) | |
4 | - (memory 1) | |
5 | - (export "memory" (memory 0)) | |
6 | - (export "main" (func $main)) | |
7 | - | |
8 | - (table | |
9 | - (export "callback") | |
10 | - anyfunc | |
11 | - (elem | |
12 | - $callback | |
13 | - ) | |
14 | - ) | |
15 | - | |
16 | - (func $main | |
17 | - (block | |
18 | - ;; Memory layout: | |
19 | - ;; 0 - 20 bytes: address (4) | |
20 | - ;; 20 - 52 bytes: value (0) | |
21 | - ;; 52 - 56 bytes: data (0x42004200) | |
22 | - ;; 56 - 60 bytes: result | |
23 | - (i32.store (i32.const 0) (i32.const 0x4)) | |
24 | - (i32.store (i32.const 52) (i32.const 0x42004200)) | |
25 | - (call $call (i64.const 2000) (i32.const 0) (i32.const 20) (i32.const 52) (i32.const 4) (i32.const 56) (i32.const 4) (i32.const 0)) | |
26 | - drop | |
27 | - ) | |
28 | - ) | |
29 | - | |
30 | - (func $callback (param $result i32) | |
31 | - (if (i32.eq (i32.const 1) (get_local $result)) | |
32 | - (return) | |
33 | - ) | |
34 | - (unreachable) | |
35 | - ) | |
36 | -) |
tests/interface/callDataCopy.json | ||
---|---|---|
@@ -1,14 +1,0 @@ | ||
1 | -{ | |
2 | - "callValue": "0x00", | |
3 | - "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721", | |
4 | - "state": { | |
5 | - "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { | |
6 | - "balance": "0x056bc75e2d63100000", | |
7 | - "code": "0x00", | |
8 | - "nonce": "0x00" | |
9 | - } | |
10 | - }, | |
11 | - "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | - "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
13 | - "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" | |
14 | -} |