git ssb

0+

wanderer🌟 / js-primea-hypervisor



Commit d9ae973728a3a94e651aa65697ce88b319c67c44

Merge pull request #86 from ewasm/ports

Implement Ports
wanderer authored on 3/2/2017, 9:33:37 AM
GitHub committed on 3/2/2017, 9:33:37 AM
Parent: bef685a92d9e75c89d7be140e22ace9a14ded1a7
Parent: bd0309940892e56fdef9d841f83777c9038348f8

Files changed

debugInterface.jschanged
deps/block.jschanged
index.jschanged
package.jsonchanged
runTx.jschanged
tests/interface/address.jsonchanged
tests/interface/balance.jsonchanged
tests/interface/basic_gas_ops.jsonchanged
tests/interface/call.jsonchanged
tests/interface/callDataCopy.jsonchanged
tests/interface/callDataSize.jsonchanged
tests/interface/callValue.jsonchanged
tests/interface/coinbase.jsonchanged
tests/interface/origin.jsonchanged
tests/interface/sstore.jsonchanged
tests/interfaceRunner.jschanged
tests/apiTests.jsadded
EVMimports.jsdeleted
EVMinterface.jsadded
codeHandler.jsadded
common.jsadded
environment.jsdeleted
defaultAgent.jsadded
hypervisor.jsadded
message.jsadded
messageQueue.jsadded
testEnvironment.jsdeleted
port.jsadded
vm.jsdeleted
portManager.jsadded
wasmAgent.jsadded
wasmDebugAgent.jsadded
debugInterface.jsView
@@ -3,9 +3,8 @@
33 /**
44 * Debug Interface
55 * This expose some functions that can help with debugging wast
66 */
7-
87 module.exports = class DebugInterface {
98 constructor (kernel) {
109 this.kernel = kernel
1110 }
deps/block.jsView
@@ -1,9 +1,4 @@
1-//
2-// This class parses a serialised Ethereum Block
3-//
4-// The input is a Buffer.
5-//
61 const Address = require('./address.js')
72 const ethUtil = require('ethereumjs-util')
83 const OldBlock = require('ethereumjs-block')
94 const U256 = require('./u256.js')
index.jsView
@@ -1,63 +1,106 @@
1+const EventEmitter = require('events')
12 const Vertex = require('merkle-trie')
2-// The Kernel Exposes this Interface to VM instances it makes
3-const Imports = require('./EVMimports.js')
4-const VM = require('./vm.js')
5-const Environment = require('./environment.js')
3+const PortManager = require('./portManager.js')
4+const codeHandler = require('./codeHandler.js')
65
7-module.exports = class Kernel extends Vertex {
6+module.exports = class Kernel extends EventEmitter {
87 constructor (opts = {}) {
9- opts.code = opts.value || opts.code
10- super(opts)
8+ super()
9+ // set up the state
10+ const state = this.state = opts.state || new Vertex()
11+ this.path = state.path
1112
12- // if code is bound to this kernel then create the interfaceAPI and the imports
13- if (opts.code) {
14- this._vm = new VM(opts.code)
15- this.imports = buildImports(this._vm, opts.interfaces)
16- }
13+ // set up the vm
14+ this.imports = opts.imports
15+ this._vm = (opts.codeHandler || codeHandler).init(opts.code || state.value)
16+ this._vmstate = 'idle'
1717
18- /**
19- * Builds a import map with an array of given interfaces
20- */
21- function buildImports (api, imports = [Imports]) {
22- return imports.reduce((obj, InterfaceConstuctor) => {
23- obj[InterfaceConstuctor.name] = new InterfaceConstuctor(api).exports
24- return obj
25- }, {})
26- }
18+ // set up ports
19+ this.ports = new PortManager(state, opts.parentPort, Kernel, this.imports)
20+ this.ports.on('message', index => {
21+ this.runNextMessage(index)
22+ })
23+ this._sentAtomicMessages = []
2724 }
2825
26+ runNextMessage (index = 0) {
27+ // load the next message from port space
28+ return this.ports.peek(index).then(message => {
29+ if (message && (message._isCyclic(this) || this._vmstate === 'idle')) {
30+ this._currentMessage = message
31+ this.ports.remove(index)
32+ return this.run(message)
33+ } else {
34+ this._vmstate = 'idle'
35+ this.emit('idle')
36+ }
37+ })
38+ }
39+
2940 /**
3041 * run the kernels code with a given enviroment
3142 * The Kernel Stores all of its state in the Environment. The Interface is used
3243 * to by the VM to retrive infromation from the Environment.
3344 */
34- async run (environment = new Environment({state: this}), imports = this.imports) {
35- await this._vm.run(environment, imports)
36- }
45+ async run (message, imports = this.imports) {
46+ function revert (oldState) {
47+ // revert the state
48+ this.state.set([], oldState)
49+ // revert all the sent messages
50+ for (let msg in this._sentAtomicMessages) {
51+ msg.revert()
52+ }
53+ }
3754
38- async messageReceiver (message) {
39- // let the code handle the message if there is code
40- if (this.code) {
41- const environment = new Environment(message)
42- let result = await this.run(environment)
43- if (!result.execption) {
44- this.state = result.state
55+ const oldState = this.state.copy()
56+ let result
57+ this._vmstate = 'running'
58+ try {
59+ result = await this._vm.run(message, this, imports) || {}
60+ } catch (e) {
61+ result = {
62+ exception: true,
63+ exceptionError: e
4564 }
46- } else if (message.to.length) {
47- // else forward the message on to the destination contract
48- let [vertex, done] = await this.state.update(message.to)
49- message.to = []
50- await vertex.kernel.messageReceiver(message)
51- done(vertex)
5265 }
66+
67+ if (message.atomic) {
68+ // if we trapped revert all the sent messages
69+ if (result.execption) {
70+ // revert to the old state
71+ revert(oldState)
72+ }
73+ message._finish()
74+ message.result().then(result => {
75+ if (result.execption) {
76+ revert()
77+ } else {
78+ this.runNextMessage(0)
79+ }
80+ })
81+
82+ if (message.hops === message.to.length || result.exception) {
83+ message._respond(result)
84+ }
85+ } else {
86+ // non-atomic messages
87+ this.runNextMessage(0)
88+ }
89+ return result
5390 }
5491
55- copy () {
56- return new Kernel({
57- state: this.state.copy(),
58- code: this.code,
59- interfaces: this.interfaces,
60- parent: this.parent
61- })
92+ async send (message) {
93+ if (message.atomic) {
94+ // record that this message has traveled thourgh this kernel. This is used
95+ // to detect re-entry
96+ message._visited(this, this._currentMessage)
97+ // recoded that this message was sent, so that we can revert it if needed
98+ this._sentAtomicMessages.push(message)
99+ }
100+ return this.ports.send(message)
62101 }
102+
103+ shutdown () {
104+ this.ports.close()
105+ }
63106 }
package.jsonView
@@ -2,8 +2,10 @@
22 "name": "ewasm-kernel",
33 "version": "0.0.0",
44 "description": "This is a JS prototype of the eWASM kernal.",
55 "scripts": {
6+ "coverage": "node --harmony ./node_modules/istanbul/lib/cli.js cover ./tests/apiTests.js",
7+ "coveralls": "npm run coverage && coveralls <coverage/lcov.info",
68 "lint": "standard",
79 "test": "node --harmony --expose-wasm ./tests/interfaceRunner.js",
810 "build": "node ./tests/buildTests.js && ./tools/wabt/out/wast2wasm ./wasm/interface.wast -o ./wasm/interface.wasm"
911 },
@@ -24,8 +26,10 @@
2426 "author": "mjbecze <mjbecze@gmail.com>",
2527 "contributors": "Alex Beregszaszi <alex@rtfs.hu>",
2628 "license": "MPL-2.0",
2729 "devDependencies": {
30+ "coveralls": "^2.11.16",
31+ "istanbul": "^0.4.5",
2832 "standard": "*",
2933 "tape": "^4.5.1"
3034 },
3135 "standard": {
runTx.jsView
@@ -1,5 +1,12 @@
1+// class runtx extends Kernel {
2+// run (tx) {
3+
14
5+// }
6+// }
7+
8+
29 // run tx; the tx message handler
310 // runTx (tx, environment = new Environment()) {
411 // this.environment = environment
512
tests/interface/address.jsonView
@@ -8,6 +8,7 @@
88 "nonce": "0x00"
99 }
1010 },
1111 "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
1213 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
1314 }
tests/interface/balance.jsonView
@@ -8,6 +8,7 @@
88 "nonce": "0x00"
99 }
1010 },
1111 "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
1213 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
1314 }
tests/interface/basic_gas_ops.jsonView
@@ -8,7 +8,8 @@
88 "nonce": "0x00"
99 }
1010 },
1111 "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
1213 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
1314 "gasLeft": 1000
1415 }
tests/interface/call.jsonView
@@ -8,6 +8,7 @@
88 "nonce": "0x00"
99 }
1010 },
1111 "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
1213 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
1314 }
tests/interface/callDataCopy.jsonView
@@ -8,6 +8,7 @@
88 "nonce": "0x00"
99 }
1010 },
1111 "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
12+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
1213 "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b"
1314 }
tests/interface/callDataSize.jsonView
@@ -1,5 +1,6 @@
11 {
2+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
23 "callValue": "0x00",
34 "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721",
45 "state": {
56 "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
tests/interface/callValue.jsonView
@@ -1,5 +1,6 @@
11 {
2+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
23 "callValue": "0x056bc75e2d63100000",
34 "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721",
45 "state": {
56 "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
tests/interface/coinbase.jsonView
@@ -1,5 +1,6 @@
11 {
2+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
23 "callValue": "0x00",
34 "callData": "0x00",
45 "state": {
56 "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
tests/interface/origin.jsonView
@@ -1,5 +1,6 @@
11 {
2+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
23 "callValue": "0x00",
34 "callData": "0x00",
45 "state": {
56 "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
tests/interface/sstore.jsonView
@@ -1,5 +1,6 @@
11 {
2+ "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b",
23 "callValue": "0x00",
34 "callData": "0x00",
45 "state": {
56 "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": {
tests/interfaceRunner.jsView
@@ -2,43 +2,38 @@
22 const fs = require('fs')
33 const path = require('path')
44 const Vertex = require('merkle-trie')
55 const Address = require('../deps/address')
6+const Block = require('../deps/block')
67 const U256 = require('../deps/u256')
7-const Block = require('../deps/block.js')
8-
9-const Kernel = require('../index.js')
10-const Environment = require('../testEnvironment.js')
11-
8+// TODO remove fakeblockchain
9+const fakeBlockChain = require('../fakeBlockChain.js')
10+const Hypervisor = require('../hypervisor.js')
11+const Message = require('../message.js')
12+const common = require('../common')
1213 const dir = path.join(__dirname, '/interface')
14+const EVMinterface = require('../EVMinterface.js')
1315 // get the test names
1416 let tests = fs.readdirSync(dir).filter((file) => file.endsWith('.wast'))
17+// tests = ['sstore.wast']
1518
1619 runTests(tests)
1720
1821 function runTests (tests) {
1922 for (let testName of tests) {
2023 testName = testName.split('.')[0]
2124 tape(testName, async (t) => {
22- // Compile Command
23- const rootVertex = new Vertex()
25+ const hypervisor = new Hypervisor(new Vertex(), [EVMinterface])
26+ const rootVertex = hypervisor.state
2427 const code = fs.readFileSync(`${dir}/${testName}.wasm`)
2528 const envData = JSON.parse(fs.readFileSync(`${dir}/${testName}.json`).toString())
2629
27- envData.block = new Block()
28- envData.caller = new Address(envData.caller)
29- envData.address = new Address(envData.address)
30- envData.block.header.coinbase = new Address(envData.coinbase)
31- envData.origin = new Address(envData.origin)
32- envData.callData = new Buffer(envData.callData.slice(2), 'hex')
33- envData.callValue = new U256(envData.callValue)
34-
3530 for (let address in envData.state) {
3631 const account = envData.state[address]
3732 const accountVertex = new Vertex()
3833
3934 accountVertex.set('code', new Vertex({
40- value: new Buffer(account.code.slice(2), 'hex')
35+ value: code
4136 }))
4237
4338 accountVertex.set('balance', new Vertex({
4439 value: new Buffer(account.balance.slice(2), 'hex')
@@ -49,26 +44,29 @@
4944 value: new Buffer(account.storage[key].slice(2), 'hex')
5045 }))
5146 }
5247
53- const path = [...new Buffer(address.slice(2), 'hex')]
48+ const path = ['accounts', address]
5449 rootVertex.set(path, accountVertex)
5550 }
5651
57- envData.state = await rootVertex.get([...envData.address.toBuffer()])
58- const kernel = new Kernel({code: code})
59- const env = new Environment(envData)
52+ rootVertex.set('blockchain', new Vertex({
53+ value: fakeBlockChain
54+ }))
6055
61- try {
62- await kernel.run(env)
63- } catch (e) {
64- t.fail('Exception: ' + e)
65- console.error('FAIL')
66- console.error(e)
67- } finally {
68- t.pass(testName)
69- console.log('done')
70- }
56+ const block = new Block()
57+ block.header.coinbase = new Address(envData.coinbase)
58+
59+ const message = new Message()
60+ message.to = ['accounts', envData.caller, common.PARENT, envData.address, 'code']
61+ message.data = new Buffer(envData.callData.slice(2), 'hex')
62+ message.value = new U256(envData.callValue)
63+ message.gas = 1000000
64+ message.block = block
65+ message.blockchain = fakeBlockChain
66+
67+ const results = await hypervisor.send(message)
68+ t.equals(results.exception, undefined)
7169 t.end()
7270 })
7371 }
7472 }
tests/apiTests.jsView
@@ -1,0 +1,53 @@
1+const tape = require('tape')
2+const Hypervisor = require('../hypervisor.js')
3+const Message = require('../message.js')
4+const Vertex = require('merkle-trie')
5+
6+tape('send and reciving messages', async t => {
7+ try {
8+ const hypervisor = new Hypervisor()
9+ const path = ['one', 'two', 'three']
10+ hypervisor.set(path, {
11+ run: message => {
12+ t.pass('got message')
13+ t.end()
14+ return {}
15+ }
16+ })
17+ hypervisor.send(new Message({
18+ to: path
19+ }))
20+ } catch (e) {
21+ console.log(e)
22+ }
23+})
24+
25+tape('reverts', async t => {
26+ const hypervisor = new Hypervisor()
27+ const path = ['one', 'two', 'three']
28+ const path2 = ['one', 'two', 'three', 'four']
29+ hypervisor.set(path, {
30+ run: async (message, kernel) => {
31+ await kernel.send(new Message({
32+ to: ['four']
33+ }))
34+ throw new Error('vm exception')
35+ }
36+ })
37+
38+ hypervisor.set(path2, {
39+ run: (message, kernel) => {
40+ kernel.stateInterface.set('key', new Vertex({
41+ value: 'value'
42+ }))
43+ }
44+ })
45+
46+ const message = new Message({
47+ to: path
48+ })
49+ hypervisor.send(message)
50+ const result = await message.result()
51+ t.equals(result.exception, true)
52+ t.end()
53+})
EVMimports.jsView
@@ -1,661 +1,0 @@
1-/**
2- * This is the Ethereum interface that is exposed to the WASM instance which
3- * enables to interact with the Ethereum Environment
4- */
5-const fs = require('fs')
6-const path = require('path')
7-const ethUtil = require('ethereumjs-util')
8-const Vertex = require('merkle-trie')
9-const U256 = require('./deps/u256.js')
10-
11-const U128_SIZE_BYTES = 16
12-const ADDRESS_SIZE_BYTES = 20
13-const U256_SIZE_BYTES = 32
14-
15-// The interface exposed to the WebAessembly Core
16-module.exports = class Interface {
17- constructor (kernel) {
18- this.kernel = kernel
19- const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm'))
20- const shimMod = WebAssembly.Module(shimBin)
21- this.shims = WebAssembly.Instance(shimMod, {
22- 'interface': {
23- 'useGas': this._useGas.bind(this),
24- 'getGasLeftHigh': this._getGasLeftHigh.bind(this),
25- 'getGasLeftLow': this._getGasLeftLow.bind(this),
26- 'call': this._call.bind(this)
27- }
28- })
29- }
30-
31- static get name () {
32- return 'ethereum'
33- }
34-
35- get exports () {
36- let exportMethods = [
37- // include all the public methods according to the Ethereum Environment Interface (EEI) r1
38- 'getAddress',
39- 'getBalance',
40- 'getTxOrigin',
41- 'getCaller',
42- 'getCallValue',
43- 'getCallDataSize',
44- 'callDataCopy',
45- 'callDataCopy256',
46- 'getCodeSize',
47- 'codeCopy',
48- 'getExternalCodeSize',
49- 'externalCodeCopy',
50- 'getTxGasPrice',
51- 'getBlockHash',
52- 'getBlockCoinbase',
53- 'getBlockTimestamp',
54- 'getBlockNumber',
55- 'getBlockDifficulty',
56- 'getBlockGasLimit',
57- 'log',
58- 'create',
59- 'callCode',
60- 'callDelegate',
61- 'storageStore',
62- 'storageLoad',
63- 'return',
64- 'selfDestruct'
65- ]
66- let ret = {}
67- exportMethods.forEach((method) => {
68- ret[method] = this[method].bind(this)
69- })
70-
71- // add shims
72- ret.useGas = this.shims.exports.useGas
73- ret.getGasLeft = this.shims.exports.getGasLeft
74- ret.call = this.shims.exports.call
75- return ret
76- }
77-
78- setModule (mod) {
79- this.module = mod
80- }
81-
82- /**
83- * Subtracts an amount to the gas counter
84- * @param {integer} amount the amount to subtract to the gas counter
85- */
86- _useGas (high, low) {
87- this.takeGas(from64bit(high, low))
88- }
89-
90- /**
91- * Returns the current amount of gas
92- * @return {integer}
93- */
94- _getGasLeftHigh () {
95- return Math.floor(this.kernel.environment.gasLeft / 4294967296)
96- }
97-
98- /**
99- * Returns the current amount of gas
100- * @return {integer}
101- */
102- _getGasLeftLow () {
103- return this.kernel.environment.gasLeft
104- }
105-
106- /**
107- * Gets address of currently executing account and loads it into memory at
108- * the given offset.
109- * @param {integer} offset
110- */
111- getAddress (offset) {
112- this.takeGas(2)
113-
114- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.address.toMemory())
115- }
116-
117- /**
118- * Gets balance of the given account and loads it into memory at the given
119- * offset.
120- * @param {integer} addressOffset the memory offset to laod the address
121- * @param {integer} resultOffset
122- */
123- getBalance (addressOffset, offset, cbIndex) {
124- this.takeGas(20)
125-
126- const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'balance']
127- const opPromise = this.kernel.environment.state.root.get(path)
128- .then(vertex => new U256(vertex.value))
129- .catch(() => new U256(0))
130-
131- this.kernel.pushOpsQueue(opPromise, cbIndex, balance => {
132- this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES))
133- })
134- }
135-
136- /**
137- * Gets the execution's origination address and loads it into memory at the
138- * given offset. This is the sender of original transaction; it is never an
139- * account with non-empty associated code.
140- * @param {integer} offset
141- */
142- getTxOrigin (offset) {
143- this.takeGas(2)
144-
145- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.origin.toMemory())
146- }
147-
148- /**
149- * Gets caller address and loads it into memory at the given offset. This is
150- * the address of the account that is directly responsible for this execution.
151- * @param {integer} offset
152- */
153- getCaller (offset) {
154- this.takeGas(2)
155-
156- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.caller.toMemory())
157- }
158-
159- /**
160- * Gets the deposited value by the instruction/transaction responsible for
161- * this execution and loads it into memory at the given location.
162- * @param {integer} offset
163- */
164- getCallValue (offset) {
165- this.takeGas(2)
166-
167- this.setMemory(offset, U128_SIZE_BYTES, this.kernel.environment.callValue.toMemory(U128_SIZE_BYTES))
168- }
169-
170- /**
171- * Get size of input data in current environment. This pertains to the input
172- * data passed with the message call instruction or transaction.
173- * @return {integer}
174- */
175- getCallDataSize () {
176- this.takeGas(2)
177-
178- return this.kernel.environment.callData.length
179- }
180-
181- /**
182- * Copys the input data in current environment to memory. This pertains to
183- * the input data passed with the message call instruction or transaction.
184- * @param {integer} offset the offset in memory to load into
185- * @param {integer} dataOffset the offset in the input data
186- * @param {integer} length the length of data to copy
187- */
188- callDataCopy (offset, dataOffset, length) {
189- this.takeGas(3 + Math.ceil(length / 32) * 3)
190-
191- if (length > 0 && offset >= 0 && dataOffset >= 0) {
192- const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + length)
193- this.setMemory(offset, length, callData)
194- }
195- }
196-
197- /**
198- * Copys the input data in current environment to memory. This pertains to
199- * the input data passed with the message call instruction or transaction.
200- * @param {integer} offset the offset in memory to load into
201- * @param {integer} dataOffset the offset in the input data
202- */
203- callDataCopy256 (offset, dataOffset) {
204- this.takeGas(3)
205-
206- const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + 32)
207- this.setMemory(offset, U256_SIZE_BYTES, callData)
208- }
209-
210- /**
211- * Gets the size of code running in current environment.
212- * @return {interger}
213- */
214- getCodeSize (cbIndex) {
215- this.takeGas(2)
216-
217- const opPromise = this.kernel.environment.state
218- .get('code')
219- .then(vertex => vertex.value.length)
220-
221- // wait for all the prevouse async ops to finish before running the callback
222- this.kernel.pushOpsQueue(opPromise, cbIndex, length => length)
223- }
224-
225- /**
226- * Copys the code running in current environment to memory.
227- * @param {integer} offset the memory offset
228- * @param {integer} codeOffset the code offset
229- * @param {integer} length the length of code to copy
230- */
231- codeCopy (resultOffset, codeOffset, length, cbIndex) {
232- this.takeGas(3 + Math.ceil(length / 32) * 3)
233-
234- let opPromise
235-
236- if (length) {
237- opPromise = this.kernel.environment.state
238- .get('code')
239- .then(vertex => vertex.value)
240- } else {
241- opPromise = Promise.resolve([])
242- }
243-
244- // wait for all the prevouse async ops to finish before running the callback
245- this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
246- if (code.length) {
247- code = code.slice(codeOffset, codeOffset + length)
248- this.setMemory(resultOffset, length, code)
249- }
250- })
251- }
252-
253- /**
254- * Get size of an account’s code.
255- * @param {integer} addressOffset the offset in memory to load the address from
256- * @return {integer}
257- */
258- getExternalCodeSize (addressOffset, cbOffset) {
259- this.takeGas(20)
260- const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
261- const opPromise = this.kernel.environment.state.root
262- .get(address)
263- .then(vertex => vertex.value.length)
264- .catch(() => 0)
265-
266- // wait for all the prevouse async ops to finish before running the callback
267- this.kernel.pushOpsQueue(opPromise, cbOffset, length => length)
268- }
269-
270- /**
271- * Copys the code of an account to memory.
272- * @param {integer} addressOffset the memory offset of the address
273- * @param {integer} resultOffset the memory offset
274- * @param {integer} codeOffset the code offset
275- * @param {integer} length the length of code to copy
276- */
277- externalCodeCopy (addressOffset, resultOffset, codeOffset, length, cbIndex) {
278- this.takeGas(20 + Math.ceil(length / 32) * 3)
279-
280- const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
281- let opPromise
282-
283- if (length) {
284- opPromise = this.kernel.environment.state.root
285- .get(address)
286- .then(vertex => vertex.value)
287- .catch(() => [])
288- } else {
289- opPromise = Promise.resolve([])
290- }
291-
292- // wait for all the prevouse async ops to finish before running the callback
293- this.kernel.pushOpsQueue(opPromise, cbIndex, code => {
294- if (code.length) {
295- code = code.slice(codeOffset, codeOffset + length)
296- this.setMemory(resultOffset, length, code)
297- }
298- })
299- }
300-
301- /**
302- * Gets price of gas in current environment.
303- * @return {integer}
304- */
305- getTxGasPrice () {
306- this.takeGas(2)
307-
308- return this.kernel.environment.gasPrice
309- }
310-
311- /**
312- * Gets the hash of one of the 256 most recent complete blocks.
313- * @param {integer} number which block to load
314- * @param {integer} offset the offset to load the hash into
315- */
316- getBlockHash (number, offset, cbOffset) {
317- this.takeGas(20)
318-
319- const diff = this.kernel.environment.block.number - number
320- let opPromise
321-
322- if (diff > 256 || diff <= 0) {
323- opPromise = Promise.resolve(new U256(0))
324- } else {
325- opPromise = this.kernel.environment.getBlockHash(number)
326- }
327-
328- // wait for all the prevouse async ops to finish before running the callback
329- this.kernel.pushOpsQueue(opPromise, cbOffset, hash => {
330- this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
331- })
332- }
333-
334- /**
335- * Gets the block’s beneficiary address and loads into memory.
336- * @param offset
337- */
338- getBlockCoinbase (offset) {
339- this.takeGas(2)
340-
341- this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.block.header.coinbase)
342- }
343-
344- /**
345- * Get the block’s timestamp.
346- * @return {integer}
347- */
348- getBlockTimestamp () {
349- this.takeGas(2)
350-
351- return this.kernel.environment.block.timestamp
352- }
353-
354- /**
355- * Get the block’s number.
356- * @return {integer}
357- */
358- getBlockNumber () {
359- this.takeGas(2)
360-
361- return this.kernel.environment.block.number
362- }
363-
364- /**
365- * Get the block’s difficulty.
366- * @return {integer}
367- */
368- getBlockDifficulty (offset) {
369- this.takeGas(2)
370-
371- this.setMemory(offset, U256_SIZE_BYTES, this.kernel.environment.block.difficulty.toMemory())
372- }
373-
374- /**
375- * Get the block’s gas limit.
376- * @return {integer}
377- */
378- getBlockGasLimit () {
379- this.takeGas(2)
380-
381- return this.kernel.environment.block.gasLimit
382- }
383-
384- /**
385- * Creates a new log in the current environment
386- * @param {integer} dataOffset the offset in memory to load the memory
387- * @param {integer} length the data length
388- * @param {integer} number of topics
389- */
390- log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
391- if (numberOfTopics < 0 || numberOfTopics > 4) {
392- throw new Error('Invalid numberOfTopics')
393- }
394-
395- this.takeGas(375 + length * 8 + numberOfTopics * 375)
396-
397- const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
398- const topics = []
399-
400- if (numberOfTopics > 0) {
401- topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
402- }
403-
404- if (numberOfTopics > 1) {
405- topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
406- }
407-
408- if (numberOfTopics > 2) {
409- topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
410- }
411-
412- if (numberOfTopics > 3) {
413- topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
414- }
415-
416- this.kernel.environment.logs.push({
417- data: data,
418- topics: topics
419- })
420- }
421-
422- /**
423- * Creates a new contract with a given value.
424- * @param {integer} valueOffset the offset in memory to the value from
425- * @param {integer} dataOffset the offset to load the code for the new contract from
426- * @param {integer} length the data length
427- * @param (integer} resultOffset the offset to write the new contract address to
428- * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
429- */
430- create (valueOffset, dataOffset, length, resultOffset, cbIndex) {
431- this.takeGas(32000)
432-
433- const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
434- // if (length) {
435- // const code = this.getMemory(dataOffset, length).slice(0)
436- // }
437-
438- let opPromise
439-
440- if (value.gt(this.kernel.environment.value)) {
441- opPromise = Promise.resolve(new Buffer(20).fill(0))
442- } else {
443- // todo actully run the code
444- opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.address, this.kernel.environment.nonce))
445- }
446-
447- // wait for all the prevouse async ops to finish before running the callback
448- this.kernel.pushOpsQueue(opPromise, cbIndex, address => {
449- this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
450- })
451- }
452-
453- /**
454- * Sends a message with arbiatary data to a given address path
455- * @param {integer} addressOffset the offset to load the address path from
456- * @param {integer} valueOffset the offset to load the value from
457- * @param {integer} dataOffset the offset to load data from
458- * @param {integer} dataLength the length of data
459- * @param {integer} resultOffset the offset to store the result data at
460- * @param {integer} resultLength
461- * @param {integer} gas
462- * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
463- */
464- _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
465- this.takeGas(40)
466-
467- const gas = from64bit(gasHigh, gasLow)
468- // Load the params from mem
469- const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
470- const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES))
471-
472- // Special case for non-zero value; why does this exist?
473- if (!value.isZero()) {
474- this.takeGas(9000 - 2300 + gas)
475- this.takeGas(-gas)
476- }
477-
478- let opPromise = this.kernel.environment.state.root.get(address)
479- .catch(() => {
480- // why does this exist?
481- this.takeGas(25000)
482- })
483-
484- // wait for all the prevouse async ops to finish before running the callback
485- this.kernel.pushOpsQueue(opPromise, cbIndex, () => {
486- return 1
487- })
488- }
489-
490- /**
491- * Message-call into this account with an alternative account’s code.
492- * @param {integer} addressOffset the offset to load the address path from
493- * @param {integer} valueOffset the offset to load the value from
494- * @param {integer} dataOffset the offset to load data from
495- * @param {integer} dataLength the length of data
496- * @param {integer} resultOffset the offset to store the result data at
497- * @param {integer} resultLength
498- * @param {integer} gas
499- * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
500- */
501- callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
502- this.takeGas(40)
503- // Load the params from mem
504- const path = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
505- const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
506-
507- // Special case for non-zero value; why does this exist?
508- if (!value.isZero()) {
509- this.takeGas(6700)
510- }
511-
512- // TODO: should be message?
513- const opPromise = this.kernel.environment.state.root.get(path)
514- .catch(() => {
515- // TODO: handle errors
516- // the value was not found
517- return null
518- })
519-
520- this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
521- return 1
522- })
523- }
524-
525- /**
526- * Message-call into this account with an alternative account’s code, but
527- * persisting the current values for sender and value.
528- * @param {integer} gas
529- * @param {integer} addressOffset the offset to load the address path from
530- * @param {integer} valueOffset the offset to load the value from
531- * @param {integer} dataOffset the offset to load data from
532- * @param {integer} dataLength the length of data
533- * @param {integer} resultOffset the offset to store the result data at
534- * @param {integer} resultLength
535- * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
536- */
537- callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
538- // FIXME: count properly
539- this.takeGas(40)
540-
541- const data = this.getMemory(dataOffset, dataLength).slice(0)
542- const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
543- const [errorCode, result] = this.environment.callDelegate(gas, address, data)
544- this.setMemory(resultOffset, resultLength, result)
545- return errorCode
546- }
547-
548- /**
549- * store a value at a given path in long term storage which are both loaded
550- * from Memory
551- * @param {interger} pathOffest the memory offset to load the the path from
552- * @param {interger} valueOffset the memory offset to load the value from
553- */
554- storageStore (pathOffset, valueOffset, cbIndex) {
555- this.takeGas(5000)
556- const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
557- // copy the value
558- const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
559- const valIsZero = value.every((i) => i === 0)
560- const opPromise = this.kernel.environment.state.get(path)
561- .then(vertex => vertex.value)
562- .catch(() => null)
563-
564- this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => {
565- if (valIsZero && oldValue) {
566- // delete a value
567- this.kernel.environment.gasRefund += 15000
568- this.kernel.environment.state.del(path)
569- } else {
570- if (!valIsZero && !oldValue) {
571- // creating a new value
572- this.takeGas(15000)
573- }
574- // update
575- this.kernel.environment.state.set(path, new Vertex({
576- value: value
577- }))
578- }
579- })
580- }
581-
582- /**
583- * reterives a value at a given path in long term storage
584- * @param {interger} pathOffest the memory offset to load the the path from
585- * @param {interger} resultOffset the memory offset to load the value from
586- */
587- storageLoad (pathOffset, resultOffset, cbIndex) {
588- this.takeGas(50)
589-
590- // convert the path to an array
591- const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)]
592- // get the value from the state
593- const opPromise = this.kernel.environment.state.get(path)
594- .then(vertex => vertex.value)
595- .catch(() => new Uint8Array(32))
596-
597- this.kernel.pushOpsQueue(opPromise, cbIndex, value => {
598- this.setMemory(resultOffset, U256_SIZE_BYTES, value)
599- })
600- }
601-
602- /**
603- * Halt execution returning output data.
604- * @param {integer} offset the offset of the output data.
605- * @param {integer} length the length of the output data.
606- */
607- return (offset, length) {
608- this.kernel.environment.returnValue = this.getMemory(offset, length).slice(0)
609- }
610-
611- /**
612- * Halt execution and register account for later deletion giving the remaining
613- * balance to an address path
614- * @param {integer} offset the offset to load the address from
615- */
616- selfDestruct (addressOffset) {
617- this.kernel.environment.selfDestruct = true
618- this.kernel.environment.selfDestructAddress = this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)
619- this.kernel.environment.gasRefund += 24000
620- }
621-
622- getMemory (offset, length) {
623- if (offset >= 0 && length > 0) {
624- return new Uint8Array(this.kernel.memory, offset, length)
625- } else {
626- return new Uint8Array([])
627- }
628- }
629-
630- setMemory (offset, length, value) {
631- if (offset >= 0 && length > 0) {
632- const memory = new Uint8Array(this.kernel.memory, offset, length)
633- memory.set(value)
634- }
635- }
636-
637- /*
638- * Takes gas from the tank. Only needs to check if there's gas left to be taken,
639- * because every caller of this method is trusted.
640- */
641- takeGas (amount) {
642- if (this.kernel.environment.gasLeft < amount) {
643- throw new Error('Ran out of gas')
644- }
645- this.kernel.environment.gasLeft -= amount
646- }
647-}
648-
649-// converts a 64 bit number to a JS number
650-function from64bit (high, low) {
651- if (high < 0) {
652- // convert from a 32-bit two's compliment
653- high = 0x100000000 - high
654- }
655- if (low < 0) {
656- // convert from a 32-bit two's compliment
657- low = 0x100000000 - low
658- }
659- // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
660- return (high * 4294967296) + low
661-}
EVMinterface.jsView
@@ -1,0 +1,657 @@
1+/**
2+ * This is the Ethereum interface that is exposed to the WASM instance which
3+ * enables to interact with the Ethereum Environment
4+ */
5+const fs = require('fs')
6+const path = require('path')
7+const ethUtil = require('ethereumjs-util')
8+const Vertex = require('merkle-trie')
9+const U256 = require('./deps/u256.js')
10+const Message = require('./message.js')
11+const common = require('./common.js')
12+
13+const U128_SIZE_BYTES = 16
14+const ADDRESS_SIZE_BYTES = 20
15+const U256_SIZE_BYTES = 32
16+
17+// The interface exposed to the WebAessembly VM
18+module.exports = class Interface {
19+ constructor (opts) {
20+ opts.response.gasRefund = 0
21+ this.message = opts.message
22+ this.kernel = opts.kernel
23+ this.vm = opts.vm
24+ this.results = opts.response
25+ const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm'))
26+ const shimMod = WebAssembly.Module(shimBin)
27+ this.shims = WebAssembly.Instance(shimMod, {
28+ 'interface': {
29+ 'useGas': this._useGas.bind(this),
30+ 'getGasLeftHigh': this._getGasLeftHigh.bind(this),
31+ 'getGasLeftLow': this._getGasLeftLow.bind(this),
32+ 'call': this._call.bind(this)
33+ }
34+ })
35+ }
36+
37+ static get name () {
38+ return 'ethereum'
39+ }
40+
41+ get exports () {
42+ let exportMethods = [
43+ // include all the public methods according to the Ethereum Environment Interface (EEI) r1
44+ 'getAddress',
45+ 'getBalance',
46+ 'getTxOrigin',
47+ 'getCaller',
48+ 'getCallValue',
49+ 'getCallDataSize',
50+ 'callDataCopy',
51+ 'callDataCopy256',
52+ 'getCodeSize',
53+ 'codeCopy',
54+ 'getExternalCodeSize',
55+ 'externalCodeCopy',
56+ 'getTxGasPrice',
57+ 'getBlockHash',
58+ 'getBlockCoinbase',
59+ 'getBlockTimestamp',
60+ 'getBlockNumber',
61+ 'getBlockDifficulty',
62+ 'getBlockGasLimit',
63+ 'log',
64+ 'create',
65+ 'callCode',
66+ 'callDelegate',
67+ 'storageStore',
68+ 'storageLoad',
69+ 'return',
70+ 'selfDestruct'
71+ ]
72+ let ret = {}
73+ exportMethods.forEach((method) => {
74+ ret[method] = this[method].bind(this)
75+ })
76+
77+ // add shims
78+ ret.useGas = this.shims.exports.useGas
79+ ret.getGasLeft = this.shims.exports.getGasLeft
80+ ret.call = this.shims.exports.call
81+ return ret
82+ }
83+
84+ setModule (mod) {
85+ this.module = mod
86+ }
87+
88+ /**
89+ * Subtracts an amount to the gas counter
90+ * @param {integer} amount the amount to subtract to the gas counter
91+ */
92+ _useGas (high, low) {
93+ this.takeGas(from64bit(high, low))
94+ }
95+
96+ /**
97+ * Returns the current amount of gas
98+ * @return {integer}
99+ */
100+ _getGasLeftHigh () {
101+ return Math.floor(this.message.gas / 4294967296)
102+ }
103+
104+ /**
105+ * Returns the current amount of gas
106+ * @return {integer}
107+ */
108+ _getGasLeftLow () {
109+ return this.message.gas
110+ }
111+
112+ /**
113+ * Gets address of currently executing account and loads it into memory at
114+ * the given offset.
115+ * @param {integer} offset
116+ */
117+ getAddress (offset) {
118+ this.takeGas(2)
119+ const path = this.kernel.path
120+ this.setMemory(offset, ADDRESS_SIZE_BYTES, new Buffer(path[1].slice(2), 'hex'))
121+ }
122+
123+ /**
124+ * Gets balance of the given account and loads it into memory at the given
125+ * offset.
126+ * @param {integer} addressOffset the memory offset to laod the address
127+ * @param {integer} resultOffset
128+ */
129+ getBalance (addressOffset, offset, cbIndex) {
130+ this.takeGas(20)
131+
132+ const path = [common.PARENT, common.PARENT, '0x' + new Buffer(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)).toString('hex')]
133+ const opPromise = this.kernel.send(new Message({
134+ to: path,
135+ data: {
136+ getValue: 'balance'
137+ },
138+ sync: true
139+ }))
140+ .catch(() => new Buffer([]))
141+
142+ this.vm.pushOpsQueue(opPromise, cbIndex, balance => {
143+ this.setMemory(offset, U128_SIZE_BYTES, new U256(balance).toMemory(U128_SIZE_BYTES))
144+ })
145+ }
146+
147+ /**
148+ * Gets the execution's origination address and loads it into memory at the
149+ * given offset. This is the sender of original transaction; it is never an
150+ * account with non-empty associated code.
151+ * @param {integer} offset
152+ */
153+ getTxOrigin (offset) {
154+ this.takeGas(2)
155+
156+ const origin = new Buffer(this.message.from[2].slice(2), 'hex')
157+ this.setMemory(offset, ADDRESS_SIZE_BYTES, origin)
158+ }
159+
160+ /**
161+ * Gets caller address and loads it into memory at the given offset. This is
162+ * the address of the account that is directly responsible for this execution.
163+ * @param {integer} offset
164+ */
165+ getCaller (offset) {
166+ this.takeGas(2)
167+ const caller = this.message.from[2]
168+ this.setMemory(offset, ADDRESS_SIZE_BYTES, new Buffer(caller.slice(2), 'hex'))
169+ }
170+
171+ /**
172+ * Gets the deposited value by the instruction/transaction responsible for
173+ * this execution and loads it into memory at the given location.
174+ * @param {integer} offset
175+ */
176+ getCallValue (offset) {
177+ this.takeGas(2)
178+
179+ this.setMemory(offset, U128_SIZE_BYTES, this.message.value.toMemory(U128_SIZE_BYTES))
180+ }
181+
182+ /**
183+ * Get size of input data in current environment. This pertains to the input
184+ * data passed with the message call instruction or transaction.
185+ * @return {integer}
186+ */
187+ getCallDataSize () {
188+ this.takeGas(2)
189+
190+ return this.message.data.length
191+ }
192+
193+ /**
194+ * Copys the input data in current environment to memory. This pertains to
195+ * the input data passed with the message call instruction or transaction.
196+ * @param {integer} offset the offset in memory to load into
197+ * @param {integer} dataOffset the offset in the input data
198+ * @param {integer} length the length of data to copy
199+ */
200+ callDataCopy (offset, dataOffset, length) {
201+ this.takeGas(3 + Math.ceil(length / 32) * 3)
202+
203+ if (length) {
204+ const callData = this.message.data.slice(dataOffset, dataOffset + length)
205+ this.setMemory(offset, length, callData)
206+ }
207+ }
208+
209+ /**
210+ * Copys the input data in current environment to memory. This pertains to
211+ * the input data passed with the message call instruction or transaction.
212+ * @param {integer} offset the offset in memory to load into
213+ * @param {integer} dataOffset the offset in the input data
214+ */
215+ callDataCopy256 (offset, dataOffset) {
216+ this.takeGas(3)
217+ const callData = this.message.data.slice(dataOffset, dataOffset + 32)
218+ this.setMemory(offset, U256_SIZE_BYTES, callData)
219+ }
220+
221+ /**
222+ * Gets the size of code running in current environment.
223+ * @return {interger}
224+ */
225+ getCodeSize (cbIndex) {
226+ this.takeGas(2)
227+
228+ // wait for all the prevouse async ops to finish before running the callback
229+ this.vm.pushOpsQueue(this.kernel.code.length, cbIndex, length => length)
230+ }
231+
232+ /**
233+ * Copys the code running in current environment to memory.
234+ * @param {integer} offset the memory offset
235+ * @param {integer} codeOffset the code offset
236+ * @param {integer} length the length of code to copy
237+ */
238+ codeCopy (resultOffset, codeOffset, length, cbIndex) {
239+ this.takeGas(3 + Math.ceil(length / 32) * 3)
240+
241+ // wait for all the prevouse async ops to finish before running the callback
242+ this.vm.pushOpsQueue(this.kernel.code, cbIndex, code => {
243+ if (code.length) {
244+ code = code.slice(codeOffset, codeOffset + length)
245+ this.setMemory(resultOffset, length, code)
246+ }
247+ })
248+ }
249+
250+ /**
251+ * Get size of an account’s code.
252+ * @param {integer} addressOffset the offset in memory to load the address from
253+ * @return {integer}
254+ */
255+ getExternalCodeSize (addressOffset, cbOffset) {
256+ this.takeGas(20)
257+ const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
258+ const opPromise = this.kernel.sendMessage(common.ROOT, common.getterMessage('code', address))
259+ .then(vertex => vertex.value.length)
260+ .catch(() => 0)
261+
262+ // wait for all the prevouse async ops to finish before running the callback
263+ this.vm.pushOpsQueue(opPromise, cbOffset, length => length)
264+ }
265+
266+ /**
267+ * Copys the code of an account to memory.
268+ * @param {integer} addressOffset the memory offset of the address
269+ * @param {integer} resultOffset the memory offset
270+ * @param {integer} codeOffset the code offset
271+ * @param {integer} length the length of code to copy
272+ */
273+ externalCodeCopy (addressOffset, resultOffset, codeOffset, length, cbIndex) {
274+ this.takeGas(20 + Math.ceil(length / 32) * 3)
275+
276+ const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
277+ let opPromise
278+
279+ if (length) {
280+ opPromise = this.kernel.sendMessage(common.ROOT, common.getterMessage('code', address))
281+ .get(address)
282+ .then(vertex => vertex.value)
283+ .catch(() => [])
284+ } else {
285+ opPromise = Promise.resolve([])
286+ }
287+
288+ // wait for all the prevouse async ops to finish before running the callback
289+ this.vm.pushOpsQueue(opPromise, cbIndex, code => {
290+ if (code.length) {
291+ code = code.slice(codeOffset, codeOffset + length)
292+ this.setMemory(resultOffset, length, code)
293+ }
294+ })
295+ }
296+
297+ /**
298+ * Gets price of gas in current environment.
299+ * @return {integer}
300+ */
301+ getTxGasPrice () {
302+ this.takeGas(2)
303+
304+ return this.message.gasPrice
305+ }
306+
307+ /**
308+ * Gets the hash of one of the 256 most recent complete blocks.
309+ * @param {integer} number which block to load
310+ * @param {integer} offset the offset to load the hash into
311+ */
312+ getBlockHash (number, offset, cbOffset) {
313+ this.takeGas(20)
314+
315+ const diff = this.message.block.number - number
316+ let opPromise
317+
318+ if (diff > 256 || diff <= 0) {
319+ opPromise = Promise.resolve(new U256(0))
320+ } else {
321+ opPromise = this.state.get(['blockchain', number]).then(vertex => vertex.hash())
322+ }
323+
324+ // wait for all the prevouse async ops to finish before running the callback
325+ this.vm.pushOpsQueue(opPromise, cbOffset, hash => {
326+ this.setMemory(offset, U256_SIZE_BYTES, hash.toMemory())
327+ })
328+ }
329+
330+ /**
331+ * Gets the block’s beneficiary address and loads into memory.
332+ * @param offset
333+ */
334+ getBlockCoinbase (offset) {
335+ this.takeGas(2)
336+
337+ this.setMemory(offset, ADDRESS_SIZE_BYTES, this.message.block.header.coinbase)
338+ }
339+
340+ /**
341+ * Get the block’s timestamp.
342+ * @return {integer}
343+ */
344+ getBlockTimestamp () {
345+ this.takeGas(2)
346+
347+ return this.message.block.timestamp
348+ }
349+
350+ /**
351+ * Get the block’s number.
352+ * @return {integer}
353+ */
354+ getBlockNumber () {
355+ this.takeGas(2)
356+
357+ return this.message.block.number
358+ }
359+
360+ /**
361+ * Get the block’s difficulty.
362+ * @return {integer}
363+ */
364+ getBlockDifficulty (offset) {
365+ this.takeGas(2)
366+
367+ this.setMemory(offset, U256_SIZE_BYTES, this.message.block.difficulty.toMemory())
368+ }
369+
370+ /**
371+ * Get the block’s gas limit.
372+ * @return {integer}
373+ */
374+ getBlockGasLimit () {
375+ this.takeGas(2)
376+
377+ return this.message.gasLimit
378+ }
379+
380+ /**
381+ * Creates a new log in the current environment
382+ * @param {integer} dataOffset the offset in memory to load the memory
383+ * @param {integer} length the data length
384+ * @param {integer} number of topics
385+ */
386+ log (dataOffset, length, numberOfTopics, topic1, topic2, topic3, topic4) {
387+ if (numberOfTopics < 0 || numberOfTopics > 4) {
388+ throw new Error('Invalid numberOfTopics')
389+ }
390+
391+ this.takeGas(375 + length * 8 + numberOfTopics * 375)
392+
393+ const data = length ? this.getMemory(dataOffset, length).slice(0) : new Uint8Array([])
394+ const topics = []
395+
396+ if (numberOfTopics > 0) {
397+ topics.push(U256.fromMemory(this.getMemory(topic1, U256_SIZE_BYTES)))
398+ }
399+
400+ if (numberOfTopics > 1) {
401+ topics.push(U256.fromMemory(this.getMemory(topic2, U256_SIZE_BYTES)))
402+ }
403+
404+ if (numberOfTopics > 2) {
405+ topics.push(U256.fromMemory(this.getMemory(topic3, U256_SIZE_BYTES)))
406+ }
407+
408+ if (numberOfTopics > 3) {
409+ topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES)))
410+ }
411+
412+ this.kernel.sendMessage([this.kernel.root, 'logs'], new Message({
413+ data: data,
414+ topics: topics
415+ }))
416+ }
417+
418+ /**
419+ * Creates a new contract with a given value.
420+ * @param {integer} valueOffset the offset in memory to the value from
421+ * @param {integer} dataOffset the offset to load the code for the new contract from
422+ * @param {integer} length the data length
423+ * @param (integer} resultOffset the offset to write the new contract address to
424+ * @return {integer} Return 1 or 0 depending on if the VM trapped on the message or not
425+ */
426+ create (valueOffset, dataOffset, length, resultOffset, cbIndex) {
427+ this.takeGas(32000)
428+
429+ const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
430+ // if (length) {
431+ // const code = this.getMemory(dataOffset, length).slice(0)
432+ // }
433+
434+ let opPromise
435+
436+ if (value.gt(this.kernel.environment.value)) {
437+ opPromise = Promise.resolve(new Buffer(20).fill(0))
438+ } else {
439+ // todo actully run the code
440+ opPromise = Promise.resolve(ethUtil.generateAddress(this.kernel.environment.address, this.kernel.environment.nonce))
441+ }
442+
443+ // wait for all the prevouse async ops to finish before running the callback
444+ this.vm.pushOpsQueue(opPromise, cbIndex, address => {
445+ this.setMemory(resultOffset, ADDRESS_SIZE_BYTES, address)
446+ })
447+ }
448+
449+ /**
450+ * Sends a message with arbiatary data to a given address path
451+ * @param {integer} addressOffset the offset to load the address path from
452+ * @param {integer} valueOffset the offset to load the value from
453+ * @param {integer} dataOffset the offset to load data from
454+ * @param {integer} dataLength the length of data
455+ * @param {integer} resultOffset the offset to store the result data at
456+ * @param {integer} resultLength
457+ * @param {integer} gas
458+ * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
459+ */
460+ _call (gasHigh, gasLow, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
461+ this.takeGas(40)
462+ const gas = from64bit(gasHigh, gasLow)
463+ // Load the params from mem
464+ const address = [common.PARENT, common.PARENT, ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
465+ const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES))
466+
467+ // Special case for non-zero value; why does this exist?
468+ if (!value.isZero()) {
469+ this.takeGas(9000 - 2300 + gas)
470+ this.takeGas(-gas)
471+ }
472+
473+ const message = new Message({
474+ to: address,
475+ value: value
476+ })
477+
478+ const messagePromise = this.kernel.send(message).then(result => {
479+ if (result.exception) {
480+ this.takeGas(25000)
481+ }
482+ })
483+
484+ // wait for all the prevouse async ops to finish before running the callback
485+ this.vm.pushOpsQueue(messagePromise, cbIndex, () => {
486+ return 1
487+ })
488+ }
489+
490+ /**
491+ * Message-call into this account with an alternative account’s code.
492+ * @param {integer} addressOffset the offset to load the address path from
493+ * @param {integer} valueOffset the offset to load the value from
494+ * @param {integer} dataOffset the offset to load data from
495+ * @param {integer} dataLength the length of data
496+ * @param {integer} resultOffset the offset to store the result data at
497+ * @param {integer} resultLength
498+ * @param {integer} gas
499+ * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
500+ */
501+ callCode (gas, addressOffset, valueOffset, dataOffset, dataLength, resultOffset, resultLength, cbIndex) {
502+ this.takeGas(40)
503+ // Load the params from mem
504+ const path = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code']
505+ const value = U256.fromMemory(this.getMemory(valueOffset, U128_SIZE_BYTES))
506+
507+ // Special case for non-zero value; why does this exist?
508+ if (!value.isZero()) {
509+ this.takeGas(6700)
510+ }
511+
512+ // TODO: should be message?
513+ const opPromise = this.state.root.get(path)
514+ .catch(() => {
515+ // TODO: handle errors
516+ // the value was not found
517+ return null
518+ })
519+
520+ this.vm.pushOpsQueue(opPromise, cbIndex, oldValue => {
521+ return 1
522+ })
523+ }
524+
525+ /**
526+ * Message-call into this account with an alternative account’s code, but
527+ * persisting the current values for sender and value.
528+ * @param {integer} gas
529+ * @param {integer} addressOffset the offset to load the address path from
530+ * @param {integer} valueOffset the offset to load the value from
531+ * @param {integer} dataOffset the offset to load data from
532+ * @param {integer} dataLength the length of data
533+ * @param {integer} resultOffset the offset to store the result data at
534+ * @param {integer} resultLength
535+ * @return {integer} Returns 1 or 0 depending on if the VM trapped on the message or not
536+ */
537+ callDelegate (gas, addressOffset, dataOffset, dataLength, resultOffset, resultLength) {
538+ // FIXME: count properly
539+ this.takeGas(40)
540+
541+ const data = this.getMemory(dataOffset, dataLength).slice(0)
542+ const address = [...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)]
543+ const [errorCode, result] = this.environment.callDelegate(gas, address, data)
544+ this.setMemory(resultOffset, resultLength, result)
545+ return errorCode
546+ }
547+
548+ /**
549+ * store a value at a given path in long term storage which are both loaded
550+ * from Memory
551+ * @param {interger} pathOffest the memory offset to load the the path from
552+ * @param {interger} valueOffset the memory offset to load the value from
553+ */
554+ storageStore (pathOffset, valueOffset, cbIndex) {
555+ this.takeGas(5000)
556+ const key = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
557+ // copy the value
558+ const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0)
559+ const valIsZero = value.every((i) => i === 0)
560+ const opPromise = this.kernel.state.get(key)
561+ .then(vertex => vertex.value)
562+ .catch(() => null)
563+
564+ this.vm.pushOpsQueue(opPromise, cbIndex, oldValue => {
565+ if (valIsZero && oldValue) {
566+ // delete a value
567+ this.results.gasRefund += 15000
568+ this.kernel.state.del(key)
569+ } else {
570+ if (!valIsZero && !oldValue) {
571+ // creating a new value
572+ this.takeGas(15000)
573+ }
574+ // update
575+ this.kernel.state.set(key, new Vertex({
576+ value: value
577+ }))
578+ }
579+ })
580+ }
581+
582+ /**
583+ * reterives a value at a given path in long term storage
584+ * @param {interger} pathOffest the memory offset to load the the path from
585+ * @param {interger} resultOffset the memory offset to load the value from
586+ */
587+ storageLoad (pathOffset, resultOffset, cbIndex) {
588+ this.takeGas(50)
589+
590+ // convert the path to an array
591+ const key = new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')
592+ // get the value from the state
593+ const opPromise = this.kernel.state.get([key])
594+ .then(vertex => vertex.value)
595+ .catch(() => new Uint8Array(32))
596+
597+ this.vm.pushOpsQueue(opPromise, cbIndex, value => {
598+ this.setMemory(resultOffset, U256_SIZE_BYTES, value)
599+ })
600+ }
601+
602+ /**
603+ * Halt execution returning output data.
604+ * @param {integer} offset the offset of the output data.
605+ * @param {integer} length the length of the output data.
606+ */
607+ return (offset, length) {
608+ if (length) {
609+ this.results.returnValue = this.getMemory(offset, length).slice(0)
610+ }
611+ }
612+
613+ /**
614+ * Halt execution and register account for later deletion giving the remaining
615+ * balance to an address path
616+ * @param {integer} offset the offset to load the address from
617+ */
618+ selfDestruct (addressOffset) {
619+ this.results.selfDestruct = true
620+ this.results.selfDestructAddress = this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)
621+ this.results.gasRefund += 24000
622+ }
623+
624+ getMemory (offset, length) {
625+ return new Uint8Array(this.vm.memory(), offset, length)
626+ }
627+
628+ setMemory (offset, length, value) {
629+ const memory = new Uint8Array(this.vm.memory(), offset, length)
630+ memory.set(value)
631+ }
632+
633+ /*
634+ * Takes gas from the tank. Only needs to check if there's gas left to be taken,
635+ * because every caller of this method is trusted.
636+ */
637+ takeGas (amount) {
638+ if (this.message.gas < amount) {
639+ throw new Error('Ran out of gas')
640+ }
641+ this.message.gas -= amount
642+ }
643+}
644+
645+// converts a 64 bit number to a JS number
646+function from64bit (high, low) {
647+ if (high < 0) {
648+ // convert from a 32-bit two's compliment
649+ high = 0x100000000 - high
650+ }
651+ if (low < 0) {
652+ // convert from a 32-bit two's compliment
653+ low = 0x100000000 - low
654+ }
655+ // JS only bitshift 32bits, so instead of high << 32 we have high * 2 ^ 32
656+ return (high * 4294967296) + low
657+}
codeHandler.jsView
@@ -1,0 +1,46 @@
1+const Wasm = require('./wasmAgent.js')
2+
3+const defaultHandler = {
4+ test: (code) => {
5+ return !code
6+ },
7+ init: () => {
8+ return require('./defaultAgent.js')
9+ }
10+}
11+
12+const wasm = {
13+ test: (code) => {
14+ code = new Buffer(code)
15+ return code && code.slice(0, 4).toString() === '\x00asm'
16+ },
17+ init: (code) => {
18+ return new Wasm(code)
19+ }
20+}
21+
22+const javascript = {
23+ test: (code) => {
24+ return typeof code === 'object'
25+ },
26+ init: (code) => {
27+ return code
28+ }
29+}
30+
31+let codeHandlers = exports.handlers = {
32+ default: defaultHandler,
33+ wasm: wasm,
34+ javascript: javascript
35+}
36+
37+exports.init = (code) => {
38+ for (let name in codeHandlers) {
39+ try {
40+ const handler = codeHandlers[name]
41+ if (handler.test(code)) {
42+ return handler.init(code)
43+ }
44+ } catch (e) {}
45+ }
46+}
common.jsView
@@ -1,0 +1,16 @@
1+const Message = require('./message')
2+
3+exports.PARENT = Symbol('parent')
4+exports.ROOT = Symbol('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+}
environment.jsView
@@ -1,104 +1,0 @@
1-const Vertex = require('merkle-trie')
2-const Store = require('merkle-trie/store')
3-const U256 = require('./deps/u256.js')
4-const Address = require('./deps/address.js')
5-const Block = require('./deps/block.js')
6-// TODO remove fakeblockchain
7-const fakeBlockChain = require('./fakeBlockChain.js')
8-
9-module.exports = class Environment {
10- constructor (data = {}) {
11- const defaults = {
12- block: new Block(),
13- blockchain: fakeBlockChain,
14- // gas tank
15- gasPrice: 0,
16- gasLeft: 1000000,
17- gasRefund: 0,
18- // call infromation
19- address: new Address('0x0000000000000000000000000000000000000000'),
20- origin: new Address('0x0000000000000000000000000000000000000000'),
21- caller: new Address('0x0000000000000000000000000000000000000000'),
22- callValue: new U256(0),
23- callData: new Uint8Array(),
24- // the ROM
25- code: new Uint8Array(), // the current running code
26- // output calls
27- logs: [],
28- selfDestruct: false,
29- selfDestructAddress: new Address('0x0000000000000000000000000000000000000000'),
30- // more output calls
31- returnValue: new Uint8Array(),
32- state: new Vertex({store: new Store()})
33- }
34- Object.assign(this, defaults, data)
35- }
36-
37- isAccountPresent (address) {
38- // const account = this.state.get(address.toString())
39- // if (account) {
40- // return true
41- // } else {
42- // return false
43- // }
44- }
45-
46- getBalance (address) {
47- // const account = this.state.get(address.toString())
48- // if (account) {
49- // return account.get('balance')
50- // } else {
51- // return new U256()
52- // }
53- }
54-
55- getCode (address) {
56- // const account = this.state.get(address.toString())
57- // if (account) {
58- // return account.get('code')
59- // } else {
60- // return Uint8Array.from(new Buffer([]))
61- // }
62- }
63-
64- async getBlockHash (height) {
65- const block = await this.blockchain.getBlock(height)
66- return block.hash()
67- }
68-
69- set createHandler (value) {
70- this.createhandler = value
71- }
72-
73- set callHandler (value) {
74- this.callhandler = value
75- }
76-
77- // kernal
78- create (code, value) {
79- // STUB
80- return [ 1, Address.zero() ]
81- }
82-
83- call (gas, address, value, data) {
84- // FIXME: create a child environment here
85- const ret = this.root.messagehandler({
86- from: this.address,
87- to: address,
88- gasLimit: gas,
89- value: value,
90- data: data
91- })
92- return [ !!ret.executionOutcome, ret.returnValue ]
93- }
94-
95- callCode (gas, address, value, data) {
96- // STUB
97- return [ 1, new Uint8Array() ]
98- }
99-
100- delegateCall (gas, address, data) {
101- // STUB
102- return [ 1, new Uint8Array() ]
103- }
104-}
defaultAgent.jsView
@@ -1,0 +1,9 @@
1+exports.run = async (message, kernel) => {
2+ const to = message.to[message.hops]
3+ if (to) {
4+ return kernel.send(message)
5+ } else if (message.data.getValue) {
6+ console.log('get value')
7+ return (await kernel.state.get(message.data.getValue)).value
8+ }
9+}
hypervisor.jsView
@@ -1,0 +1,29 @@
1+const Kernel = require('./index.js')
2+const Vertex = require('merkle-trie')
3+// const Block = require('./deps/block.js')
4+// const blockchain = require('./fakeBlockChain.js')
5+const codeHandlers = require('./codeHandler.js')
6+
7+module.exports = class Hypervisor {
8+ constructor (state = new Vertex(), imports = []) {
9+ this.state = state
10+ this.root = new Kernel({
11+ imports: imports,
12+ state: state
13+ })
14+ }
15+
16+ set (path, kernel) {
17+ this.state.set(path, new Vertex({
18+ value: kernel
19+ }))
20+ }
21+
22+ send (message) {
23+ return this.root.send(message)
24+ }
25+
26+ addVM (type, handler) {
27+ codeHandlers.handlers.type = handler
28+ }
29+}
message.jsView
@@ -1,0 +1,49 @@
1+const U256 = require('./deps/u256.js')
2+
3+module.exports = class Message {
4+ constructor (opts = {}) {
5+ const defaults = {
6+ // call infromation
7+ to: [],
8+ from: [],
9+ data: new Uint8Array(),
10+ atomic: true,
11+ // resource info
12+ gas: new U256(0),
13+ gasPrices: new U256(0)
14+ }
15+ Object.assign(this, defaults, opts)
16+ this.hops = 0
17+ this._visitedKernels = []
18+ this._resultPromise = new Promise((resolve, reject) => {
19+ this._resolve = resolve
20+ })
21+ }
22+
23+ result () {
24+ return this._resultPromise
25+ }
26+
27+ nextPort () {
28+ return this.to[this.hops]
29+ }
30+
31+ _respond (result) {
32+ this._resolve(result)
33+ }
34+
35+ _finish () {
36+ this._visitedKernels.pop()
37+ }
38+
39+ _visited (kernel, currentMessage) {
40+ if (currentMessage && this !== currentMessage) {
41+ this._visitedKernels = currentMessage._visitedKernels
42+ }
43+ this._visitedKernels.push(kernel)
44+ }
45+
46+ _isCyclic (kernel) {
47+ return this.atomic && this._visitedKernels.some(process => process === kernel)
48+ }
49+}
messageQueue.jsView
@@ -1,0 +1,11 @@
1+module.exports = class MessageQueue {
2+ constructor (kernel) {
3+ this._queue = []
4+ }
5+
6+ add (message) {
7+ this.currentMessage = message
8+ return this.kernel.run(message)
9+ }
10+
11+}
testEnvironment.jsView
@@ -1,8 +1,0 @@
1-const Environment = require('./environment.js')
2-const fakeBlockchain = require('./fakeBlockChain')
3-
4-module.exports = class TestEnvironment extends Environment {
5- async getBlockHash (height) {
6- return fakeBlockchain.getBlock(height).hash()
7- }
8-}
port.jsView
@@ -1,0 +1,27 @@
1+const EventEmitter = require('events')
2+
3+module.exports = class Port extends EventEmitter {
4+ constructor (name) {
5+ super()
6+ this.name = name
7+ this.connected = false
8+ }
9+
10+ connect (destPort) {
11+ if (!this.connected) {
12+ this.destPort = destPort
13+ destPort.destPort = this
14+ this.connected = true
15+ }
16+ }
17+
18+ async send (message) {
19+ message.hops++
20+ this.destPort.recieve(message)
21+ }
22+
23+ async recieve (message) {
24+ message.from.push(this.name)
25+ this.emit('message', message)
26+ }
27+}
vm.jsView
@@ -1,55 +1,0 @@
1-module.exports = class VM {
2- /**
3- * The interface API is the api the exposed to interfaces. All queries about
4- * the enviroment and call to the kernel go through this API
5- */
6- constructor (code) {
7- this._module = WebAssembly.Module(code)
8- }
9-
10- /**
11- * Runs the core VM with a given environment and imports
12- */
13- run (environment, imports) {
14- this._environment = environment
15- // TODO, delete the instance once done.
16- const instance = this._instance = WebAssembly.Instance(this._module, imports)
17- if (instance.exports.main) {
18- instance.exports.main()
19- }
20- return this.onDone()
21- }
22-
23- /**
24- * returns a promise that resolves when the wasm instance is done running
25- */
26- async onDone () {
27- let prevOps
28- while (prevOps !== this._opsQueue) {
29- prevOps = this._opsQueue
30- await this._opsQueue
31- }
32- }
33-
34- /**
35- * addes an aync operation to the operations queue
36- */
37- pushOpsQueue (promise, callbackIndex, intefaceCallback) {
38- this._opsQueue = Promise.all([this._opsQueue, promise]).then(values => {
39- const result = intefaceCallback(values.pop())
40- this._instance.exports.callback.get(callbackIndex)(result)
41- })
42- }
43-
44- sendMessage (message) {
45-
46- }
47-
48- get environment () {
49- return this._environment
50- }
51-
52- get memory () {
53- return this._instance.exports.memory.buffer
54- }
55-}
portManager.jsView
@@ -1,0 +1,78 @@
1+const EventEmitter = require('events')
2+const Port = require('./port.js')
3+const common = require('./common.js')
4+
5+module.exports = class PortManager extends EventEmitter {
6+ constructor (state, destParentPort, KernelContructor, imports) {
7+ super()
8+ this._queue = []
9+ this.state = state
10+ this.imports = imports
11+ this.Kernel = KernelContructor
12+ // set up the parent port
13+ const parentPort = new Port(common.PARENT)
14+ parentPort.on('message', message => {
15+ this._recieveMessage(message)
16+ })
17+
18+ // create the cache
19+ this.cache = new Map()
20+ this.cache.set(common.PARENT, parentPort)
21+ }
22+
23+ _recieveMessage (message) {
24+ const index = this._queue.push(message) - 1
25+ this.emit('message', index)
26+ }
27+
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.state.get(name)
37+ const destKernel = new this.Kernel({
38+ state: state,
39+ parentPort: port,
40+ imports: this.imports
41+ })
42+
43+ // shutdown the kernel when it is done doing it work
44+ destKernel.on('idle', () => {
45+ destKernel.shutdown()
46+ this.cache.delete(name)
47+ })
48+
49+ // find and connect the destination port
50+ const destPort = await destKernel.ports.get(common.PARENT)
51+ port.connect(destPort)
52+ this.cache.set(name, port)
53+ }
54+ return port
55+ }
56+
57+ async peek (index = 0) {
58+ return this._queue[index]
59+ }
60+
61+ remove (index) {
62+ return this._queue.splice(index, index + 1)
63+ }
64+
65+ async send (message) {
66+ let portName = message.nextPort()
67+ const port = await this.get(portName)
68+ port.send(message)
69+ return message.result()
70+ }
71+
72+ close () {
73+ for (let port in this.cache) {
74+ port.emit('close')
75+ }
76+ this.cache.clear()
77+ }
78+}
wasmAgent.jsView
@@ -1,0 +1,67 @@
1+module.exports = class Wasm {
2+ /**
3+ * The interface API is the api the exposed to interfaces. All queries about
4+ * the enviroment and call to the kernel go through this API
5+ */
6+ constructor (code) {
7+ this._module = WebAssembly.Module(code)
8+ }
9+ /**
10+ * Runs the core VM with a given environment and imports
11+ */
12+ async run (message, kernel, imports) {
13+ const responses = {}
14+ /**
15+ * Builds a import map with an array of given interfaces
16+ */
17+ function buildImports (opts, imports) {
18+ const importMap = {}
19+ for (const Import of imports) {
20+ opts.response = responses[Import.name] = {}
21+ const newInterface = new Import(opts)
22+ importMap[Import.name] = newInterface.exports
23+ }
24+ return importMap
25+ }
26+
27+ let instance
28+
29+ const opts = {
30+ vm: {
31+ /**
32+ * adds an aync operation to the operations queue
33+ */
34+ pushOpsQueue: (promise, callbackIndex, intefaceCallback) => {
35+ this._opsQueue = Promise.all([this._opsQueue, promise]).then(values => {
36+ const result = intefaceCallback(values.pop())
37+ instance.exports.callback.get(callbackIndex)(result)
38+ })
39+ },
40+ memory: () => {
41+ return instance.exports.memory.buffer
42+ }
43+ },
44+ kernel: kernel,
45+ message: message
46+ }
47+ const initializedImports = buildImports(opts, imports)
48+ instance = WebAssembly.Instance(this._module, initializedImports)
49+
50+ if (instance.exports.main) {
51+ instance.exports.main()
52+ }
53+ await this.onDone()
54+ return responses
55+ }
56+
57+ /**
58+ * returns a promise that resolves when the wasm instance is done running
59+ */
60+ async onDone () {
61+ let prevOps
62+ while (prevOps !== this._opsQueue) {
63+ prevOps = this._opsQueue
64+ await this._opsQueue
65+ }
66+ }
67+}
wasmDebugAgent.jsView
@@ -1,0 +1,50 @@
1+const Wasm = require('./wasmAgent')
2+
3+module.exports = class Test extends Wasm {
4+ /**
5+ * Runs the core VM with a given environment and imports
6+ */
7+ async run (message, kernel, imports) {
8+ const responses = this.responses = {}
9+ /**
10+ * Builds a import map with an array of given interfaces
11+ */
12+ async function buildImports (kernelApi, kernel, imports) {
13+ const importMap = {}
14+ for (const Import of imports) {
15+ const response = responses[Import.name] = {}
16+ const newIterface = new Import(kernelApi, message, response)
17+ importMap[Import.name] = newIterface.exports
18+ // initailize the import
19+ await newIterface.initialize()
20+ }
21+ return importMap
22+ }
23+
24+ let instance
25+ const interfaceApi = this.api = {
26+ /**
27+ * adds an aync operation to the operations queue
28+ */
29+ pushOpsQueue: (promise, callbackIndex, intefaceCallback) => {
30+ this._opsQueue = Promise.all([this._opsQueue, promise]).then(values => {
31+ const result = intefaceCallback(values.pop())
32+ instance.exports.callback.get(callbackIndex)(result)
33+ })
34+ },
35+ memory: () => {
36+ return instance.exports.memory.buffer
37+ },
38+ kernel: kernel
39+ }
40+
41+ const initializedImports = await buildImports(interfaceApi, kernel, imports)
42+ this.instance = instance = WebAssembly.Instance(this._module, initializedImports)
43+
44+ if (instance.exports.main) {
45+ instance.exports.main()
46+ }
47+ await this.onDone()
48+ return responses
49+ }
50+}

Built with git-ssb-web