Commit ccc86c04a14c0346d7695e768be928faf4c41267
move to pure message passing
wanderer committed on 1/20/2017, 5:22:01 PMParent: a01401f258acd7e0b1f4ecbe82cdd1bc26048b4c
Files changed
EVMinterface.js | changed |
deps/block.js | changed |
index.js | changed |
package.json | changed |
tests/interface/address.json | changed |
tests/interface/balance.json | changed |
tests/interface/basic_gas_ops.json | changed |
tests/interface/call.json | changed |
tests/interface/callDataCopy.json | changed |
tests/interface/callDataSize.json | changed |
tests/interface/callValue.json | changed |
tests/interface/coinbase.json | changed |
tests/interface/origin.json | changed |
tests/interface/sstore.json | changed |
tests/interfaceRunner.js | changed |
vm.js | changed |
codeHandler.js | added |
defaultAgent.js | added |
environment.js | deleted |
message.js | added |
messageQueue.js | added |
testEnvironment.js | deleted |
EVMinterface.js | ||
---|---|---|
@@ -6,17 +6,22 @@ | ||
6 | 6 | const path = require('path') |
7 | 7 | const ethUtil = require('ethereumjs-util') |
8 | 8 | const Vertex = require('merkle-trie') |
9 | 9 | const U256 = require('./deps/u256.js') |
10 | +const Message = require('./message.js') | |
10 | 11 | |
11 | 12 | const U128_SIZE_BYTES = 16 |
12 | 13 | const ADDRESS_SIZE_BYTES = 20 |
13 | 14 | const U256_SIZE_BYTES = 32 |
14 | 15 | |
15 | 16 | // The interface exposed to the WebAessembly VM |
16 | 17 | module.exports = class Interface { |
17 | - constructor (kernel) { | |
18 | - this.kernel = kernel | |
18 | + constructor (api, message, state) { | |
19 | + this.message = message | |
20 | + this.kernel = api.kernel | |
21 | + this.state = api.kernel.state | |
22 | + this.api = api | |
23 | + this._results = {} | |
19 | 24 | const shimBin = fs.readFileSync(path.join(__dirname, '/wasm/interface.wasm')) |
20 | 25 | const shimMod = WebAssembly.Module(shimBin) |
21 | 26 | this.shims = WebAssembly.Instance(shimMod, { |
22 | 27 | 'interface': { |
@@ -27,13 +32,28 @@ | ||
27 | 32 | } |
28 | 33 | }) |
29 | 34 | } |
30 | 35 | |
31 | - async initialize (state) { | |
32 | - this.block = await state.root.get(['block']) | |
33 | - // this.blockchain = await state.get(['blockchain']) | |
36 | + async initialize () { | |
37 | + this.block = await this.kernel.send(this.kernel.ROOT, new Message({ | |
38 | + data: { | |
39 | + getValue: 'block' | |
40 | + }, | |
41 | + sync: true | |
42 | + })) | |
43 | + | |
44 | + this.blockchain = await this.kernel.send(this.kernel.ROOT, new Message({ | |
45 | + data: { | |
46 | + getValue: 'blockchain' | |
47 | + }, | |
48 | + sync: true | |
49 | + })) | |
34 | 50 | } |
35 | 51 | |
52 | + get results () { | |
53 | + return this._results | |
54 | + } | |
55 | + | |
36 | 56 | static get name () { |
37 | 57 | return 'ethereum' |
38 | 58 | } |
39 | 59 | |
@@ -96,17 +116,17 @@ | ||
96 | 116 | * Returns the current amount of gas |
97 | 117 | * @return {integer} |
98 | 118 | */ |
99 | 119 | _getGasLeftHigh () { |
100 | - return Math.floor(this.kernel.environment.gasLeft / 4294967296) | |
120 | + return Math.floor(this.message.gas / 4294967296) | |
101 | 121 | } |
102 | 122 | |
103 | 123 | /** |
104 | 124 | * Returns the current amount of gas |
105 | 125 | * @return {integer} |
106 | 126 | */ |
107 | 127 | _getGasLeftLow () { |
108 | - return this.kernel.environment.gasLeft | |
128 | + return this.message.gas | |
109 | 129 | } |
110 | 130 | |
111 | 131 | /** |
112 | 132 | * Gets address of currently executing account and loads it into memory at |
@@ -114,10 +134,12 @@ | ||
114 | 134 | * @param {integer} offset |
115 | 135 | */ |
116 | 136 | getAddress (offset) { |
117 | 137 | this.takeGas(2) |
118 | - | |
119 | - this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.address.toMemory()) | |
138 | + const path = this.message.to | |
139 | + path.pop() | |
140 | + const address = path.pop() | |
141 | + this.setMemory(offset, ADDRESS_SIZE_BYTES, new Buffer(address.slice(2), 'hex')) | |
120 | 142 | } |
121 | 143 | |
122 | 144 | /** |
123 | 145 | * Gets balance of the given account and loads it into memory at the given |
@@ -127,15 +149,20 @@ | ||
127 | 149 | */ |
128 | 150 | getBalance (addressOffset, offset, cbIndex) { |
129 | 151 | this.takeGas(20) |
130 | 152 | |
131 | - const path = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'balance'] | |
132 | - const opPromise = this.kernel.environment.state.root.get(path) | |
133 | - .then(vertex => new U256(vertex.value)) | |
153 | + const path = [this.kernel.PARENT, '0x' + new Buffer(this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)).toString('hex')] | |
154 | + const opPromise = this.kernel.send(this.kernel.PARENT, new Message({ | |
155 | + to: path, | |
156 | + data: { | |
157 | + getValue: 'balance' | |
158 | + }, | |
159 | + sync: true | |
160 | + })) | |
134 | 161 | .catch(() => new U256(0)) |
135 | 162 | |
136 | - this.kernel.pushOpsQueue(opPromise, cbIndex, balance => { | |
137 | - this.setMemory(offset, U128_SIZE_BYTES, balance.toMemory(U128_SIZE_BYTES)) | |
163 | + this.api.pushOpsQueue(opPromise, cbIndex, balance => { | |
164 | + this.setMemory(offset, U128_SIZE_BYTES, new U256(balance).toMemory(U128_SIZE_BYTES)) | |
138 | 165 | }) |
139 | 166 | } |
140 | 167 | |
141 | 168 | /** |
@@ -146,9 +173,9 @@ | ||
146 | 173 | */ |
147 | 174 | getTxOrigin (offset) { |
148 | 175 | this.takeGas(2) |
149 | 176 | |
150 | - this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.origin.toMemory()) | |
177 | + this.setMemory(offset, ADDRESS_SIZE_BYTES, this.message.origin.toMemory()) | |
151 | 178 | } |
152 | 179 | |
153 | 180 | /** |
154 | 181 | * Gets caller address and loads it into memory at the given offset. This is |
@@ -157,9 +184,10 @@ | ||
157 | 184 | */ |
158 | 185 | getCaller (offset) { |
159 | 186 | this.takeGas(2) |
160 | 187 | |
161 | - this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.caller.toMemory()) | |
188 | + const caller = this.message.from[1] | |
189 | + this.setMemory(offset, ADDRESS_SIZE_BYTES, new Buffer(caller.slice(2), 'hex')) | |
162 | 190 | } |
163 | 191 | |
164 | 192 | /** |
165 | 193 | * Gets the deposited value by the instruction/transaction responsible for |
@@ -168,9 +196,9 @@ | ||
168 | 196 | */ |
169 | 197 | getCallValue (offset) { |
170 | 198 | this.takeGas(2) |
171 | 199 | |
172 | - this.setMemory(offset, U128_SIZE_BYTES, this.kernel.environment.callValue.toMemory(U128_SIZE_BYTES)) | |
200 | + this.setMemory(offset, U128_SIZE_BYTES, this.message.value.toMemory(U128_SIZE_BYTES)) | |
173 | 201 | } |
174 | 202 | |
175 | 203 | /** |
176 | 204 | * Get size of input data in current environment. This pertains to the input |
@@ -179,9 +207,9 @@ | ||
179 | 207 | */ |
180 | 208 | getCallDataSize () { |
181 | 209 | this.takeGas(2) |
182 | 210 | |
183 | - return this.kernel.environment.callData.length | |
211 | + return this.message.data.length | |
184 | 212 | } |
185 | 213 | |
186 | 214 | /** |
187 | 215 | * Copys the input data in current environment to memory. This pertains to |
@@ -193,9 +221,9 @@ | ||
193 | 221 | callDataCopy (offset, dataOffset, length) { |
194 | 222 | this.takeGas(3 + Math.ceil(length / 32) * 3) |
195 | 223 | |
196 | 224 | if (length) { |
197 | - const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + length) | |
225 | + const callData = this.message.data.slice(dataOffset, dataOffset + length) | |
198 | 226 | this.setMemory(offset, length, callData) |
199 | 227 | } |
200 | 228 | } |
201 | 229 | |
@@ -206,9 +234,9 @@ | ||
206 | 234 | * @param {integer} dataOffset the offset in the input data |
207 | 235 | */ |
208 | 236 | callDataCopy256 (offset, dataOffset) { |
209 | 237 | this.takeGas(3) |
210 | - const callData = this.kernel.environment.callData.slice(dataOffset, dataOffset + 32) | |
238 | + const callData = this.message.data.slice(dataOffset, dataOffset + 32) | |
211 | 239 | this.setMemory(offset, U256_SIZE_BYTES, callData) |
212 | 240 | } |
213 | 241 | |
214 | 242 | /** |
@@ -217,9 +245,9 @@ | ||
217 | 245 | */ |
218 | 246 | getCodeSize (cbIndex) { |
219 | 247 | this.takeGas(2) |
220 | 248 | |
221 | - const opPromise = this.kernel.environment.state | |
249 | + const opPromise = this.state | |
222 | 250 | .get('code') |
223 | 251 | .then(vertex => vertex.value.length) |
224 | 252 | |
225 | 253 | // wait for all the prevouse async ops to finish before running the callback |
@@ -237,9 +265,9 @@ | ||
237 | 265 | |
238 | 266 | let opPromise |
239 | 267 | |
240 | 268 | if (length) { |
241 | - opPromise = this.kernel.environment.state | |
269 | + opPromise = this.state | |
242 | 270 | .get('code') |
243 | 271 | .then(vertex => vertex.value) |
244 | 272 | } else { |
245 | 273 | opPromise = Promise.resolve([]) |
@@ -261,9 +289,9 @@ | ||
261 | 289 | */ |
262 | 290 | getExternalCodeSize (addressOffset, cbOffset) { |
263 | 291 | this.takeGas(20) |
264 | 292 | const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code'] |
265 | - const opPromise = this.kernel.environment.state.root | |
293 | + const opPromise = this.state.root | |
266 | 294 | .get(address) |
267 | 295 | .then(vertex => vertex.value.length) |
268 | 296 | .catch(() => 0) |
269 | 297 | |
@@ -284,9 +312,9 @@ | ||
284 | 312 | const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES), 'code'] |
285 | 313 | let opPromise |
286 | 314 | |
287 | 315 | if (length) { |
288 | - opPromise = this.kernel.environment.state.root | |
316 | + opPromise = this.state.root | |
289 | 317 | .get(address) |
290 | 318 | .then(vertex => vertex.value) |
291 | 319 | .catch(() => []) |
292 | 320 | } else { |
@@ -308,9 +336,9 @@ | ||
308 | 336 | */ |
309 | 337 | getTxGasPrice () { |
310 | 338 | this.takeGas(2) |
311 | 339 | |
312 | - return this.kernel.environment.gasPrice | |
340 | + return this.message.gasPrice | |
313 | 341 | } |
314 | 342 | |
315 | 343 | /** |
316 | 344 | * Gets the hash of one of the 256 most recent complete blocks. |
@@ -325,9 +353,9 @@ | ||
325 | 353 | |
326 | 354 | if (diff > 256 || diff <= 0) { |
327 | 355 | opPromise = Promise.resolve(new U256(0)) |
328 | 356 | } else { |
329 | - opPromise = this.kernel.environment.getBlockHash(number) | |
357 | + opPromise = this.state.get(['blockchain', number]).then(vertex => vertex.hash()) | |
330 | 358 | } |
331 | 359 | |
332 | 360 | // wait for all the prevouse async ops to finish before running the callback |
333 | 361 | this.kernel.pushOpsQueue(opPromise, cbOffset, hash => { |
@@ -341,9 +369,9 @@ | ||
341 | 369 | */ |
342 | 370 | getBlockCoinbase (offset) { |
343 | 371 | this.takeGas(2) |
344 | 372 | |
345 | - this.setMemory(offset, ADDRESS_SIZE_BYTES, this.kernel.environment.block.header.coinbase) | |
373 | + this.setMemory(offset, ADDRESS_SIZE_BYTES, this.block.header.coinbase) | |
346 | 374 | } |
347 | 375 | |
348 | 376 | /** |
349 | 377 | * Get the block’s timestamp. |
@@ -416,12 +444,12 @@ | ||
416 | 444 | if (numberOfTopics > 3) { |
417 | 445 | topics.push(U256.fromMemory(this.getMemory(topic4, U256_SIZE_BYTES))) |
418 | 446 | } |
419 | 447 | |
420 | - this.kernel.environment.logs.push({ | |
448 | + this.kernel.sendMessage([this.kernel.root, 'logs'], new Message({ | |
421 | 449 | data: data, |
422 | 450 | topics: topics |
423 | - }) | |
451 | + })) | |
424 | 452 | } |
425 | 453 | |
426 | 454 | /** |
427 | 455 | * Creates a new contract with a given value. |
@@ -469,25 +497,29 @@ | ||
469 | 497 | this.takeGas(40) |
470 | 498 | |
471 | 499 | const gas = from64bit(gasHigh, gasLow) |
472 | 500 | // Load the params from mem |
473 | - const address = ['accounts', ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)] | |
501 | + const address = [this.kernel.PARENT, ...this.getMemory(addressOffset, ADDRESS_SIZE_BYTES)] | |
474 | 502 | const value = new U256(this.getMemory(valueOffset, U128_SIZE_BYTES)) |
475 | 503 | |
476 | 504 | // Special case for non-zero value; why does this exist? |
477 | 505 | if (!value.isZero()) { |
478 | 506 | this.takeGas(9000 - 2300 + gas) |
479 | 507 | this.takeGas(-gas) |
480 | 508 | } |
481 | 509 | |
482 | - let opPromise = this.kernel.environment.state.root.get(address) | |
510 | + // should be | |
511 | + let opPromise = this.kernel.send(new Message({ | |
512 | + to: address, | |
513 | + value: value | |
514 | + })) | |
483 | 515 | .catch(() => { |
484 | 516 | // why does this exist? |
485 | 517 | this.takeGas(25000) |
486 | 518 | }) |
487 | 519 | |
488 | 520 | // wait for all the prevouse async ops to finish before running the callback |
489 | - this.kernel.pushOpsQueue(opPromise, cbIndex, () => { | |
521 | + this.api.pushOpsQueue(opPromise, cbIndex, () => { | |
490 | 522 | return 1 |
491 | 523 | }) |
492 | 524 | } |
493 | 525 | |
@@ -513,16 +545,16 @@ | ||
513 | 545 | this.takeGas(6700) |
514 | 546 | } |
515 | 547 | |
516 | 548 | // TODO: should be message? |
517 | - const opPromise = this.kernel.environment.state.root.get(path) | |
549 | + const opPromise = this.state.root.get(path) | |
518 | 550 | .catch(() => { |
519 | 551 | // TODO: handle errors |
520 | 552 | // the value was not found |
521 | 553 | return null |
522 | 554 | }) |
523 | 555 | |
524 | - this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => { | |
556 | + this.api.pushOpsQueue(opPromise, cbIndex, oldValue => { | |
525 | 557 | return 1 |
526 | 558 | }) |
527 | 559 | } |
528 | 560 | |
@@ -556,28 +588,28 @@ | ||
556 | 588 | * @param {interger} valueOffset the memory offset to load the value from |
557 | 589 | */ |
558 | 590 | storageStore (pathOffset, valueOffset, cbIndex) { |
559 | 591 | this.takeGas(5000) |
560 | - const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)] | |
592 | + const path = [new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')] | |
561 | 593 | // copy the value |
562 | 594 | const value = this.getMemory(valueOffset, U256_SIZE_BYTES).slice(0) |
563 | 595 | const valIsZero = value.every((i) => i === 0) |
564 | - const opPromise = this.kernel.environment.state.get(path) | |
596 | + const opPromise = this.state.get(path) | |
565 | 597 | .then(vertex => vertex.value) |
566 | 598 | .catch(() => null) |
567 | 599 | |
568 | - this.kernel.pushOpsQueue(opPromise, cbIndex, oldValue => { | |
600 | + this.api.pushOpsQueue(opPromise, cbIndex, oldValue => { | |
569 | 601 | if (valIsZero && oldValue) { |
570 | 602 | // delete a value |
571 | 603 | this.kernel.environment.gasRefund += 15000 |
572 | - this.kernel.environment.state.del(path) | |
604 | + this.state.del(path) | |
573 | 605 | } else { |
574 | 606 | if (!valIsZero && !oldValue) { |
575 | 607 | // creating a new value |
576 | 608 | this.takeGas(15000) |
577 | 609 | } |
578 | 610 | // update |
579 | - this.kernel.environment.state.set(path, new Vertex({ | |
611 | + this.state.set(path, new Vertex({ | |
580 | 612 | value: value |
581 | 613 | })) |
582 | 614 | } |
583 | 615 | }) |
@@ -591,15 +623,15 @@ | ||
591 | 623 | storageLoad (pathOffset, resultOffset, cbIndex) { |
592 | 624 | this.takeGas(50) |
593 | 625 | |
594 | 626 | // convert the path to an array |
595 | - const path = ['storage', ...this.getMemory(pathOffset, U256_SIZE_BYTES)] | |
627 | + const path = [new Buffer(this.getMemory(pathOffset, U256_SIZE_BYTES)).toString('hex')] | |
596 | 628 | // get the value from the state |
597 | - const opPromise = this.kernel.environment.state.get(path) | |
629 | + const opPromise = this.state.get(path) | |
598 | 630 | .then(vertex => vertex.value) |
599 | 631 | .catch(() => new Uint8Array(32)) |
600 | 632 | |
601 | - this.kernel.pushOpsQueue(opPromise, cbIndex, value => { | |
633 | + this.api.pushOpsQueue(opPromise, cbIndex, value => { | |
602 | 634 | this.setMemory(resultOffset, U256_SIZE_BYTES, value) |
603 | 635 | }) |
604 | 636 | } |
605 | 637 | |
@@ -625,25 +657,25 @@ | ||
625 | 657 | this.kernel.environment.gasRefund += 24000 |
626 | 658 | } |
627 | 659 | |
628 | 660 | getMemory (offset, length) { |
629 | - return new Uint8Array(this.kernel.memory, offset, length) | |
661 | + return new Uint8Array(this.api.memory(), offset, length) | |
630 | 662 | } |
631 | 663 | |
632 | 664 | setMemory (offset, length, value) { |
633 | - const memory = new Uint8Array(this.kernel.memory, offset, length) | |
665 | + const memory = new Uint8Array(this.api.memory(), offset, length) | |
634 | 666 | memory.set(value) |
635 | 667 | } |
636 | 668 | |
637 | 669 | /* |
638 | 670 | * Takes gas from the tank. Only needs to check if there's gas left to be taken, |
639 | 671 | * because every caller of this method is trusted. |
640 | 672 | */ |
641 | 673 | takeGas (amount) { |
642 | - if (this.kernel.environment.gasLeft < amount) { | |
674 | + if (this.message.gas < amount) { | |
643 | 675 | throw new Error('Ran out of gas') |
644 | 676 | } |
645 | - this.kernel.environment.gasLeft -= amount | |
677 | + this.message.gas -= amount | |
646 | 678 | } |
647 | 679 | } |
648 | 680 | |
649 | 681 | // converts a 64 bit number to a JS number |
deps/block.js | ||
---|---|---|
@@ -1,9 +1,4 @@ | ||
1 | -// | |
2 | -// This class parses a serialised Ethereum Block | |
3 | -// | |
4 | -// The input is a Buffer. | |
5 | -// | |
6 | 1 | const Address = require('./address.js') |
7 | 2 | const ethUtil = require('ethereumjs-util') |
8 | 3 | const OldBlock = require('ethereumjs-block') |
9 | 4 | const U256 = require('./u256.js') |
index.js | ||
---|---|---|
@@ -1,58 +1,97 @@ | ||
1 | 1 | const Vertex = require('merkle-trie') |
2 | -// The Kernel Exposes this Interface to VM instances it makes | |
3 | -const defaultInterface = require('./EVMinterface.js') | |
4 | -const VM = require('./vm.js') | |
5 | -const Environment = require('./environment.js') | |
2 | +const imports = require('./EVMinterface.js') | |
3 | +const codeHandler = require('./codeHandler.js') | |
4 | +const MessageQueue = require('./messageQueue') | |
6 | 5 | |
6 | +const PARENT_SYMBOL = Symbol('parent') | |
7 | +const ROOT_SYMBOL = Symbol('root') | |
8 | + | |
7 | 9 | module.exports = class Kernel { |
8 | 10 | constructor (opts = {}) { |
9 | - this.state = opts.state || new Vertex() | |
10 | - this.state.value = opts.code || this.state.value | |
11 | - this.interfaces = opts.interfaces || [defaultInterface] | |
12 | - this._vm = new VM(this.state.value) | |
11 | + const state = this.state = opts.state || new Vertex() | |
12 | + state.value = opts.code || state.value | |
13 | + this.imports = opts.imports || [imports] | |
14 | + this.parent = opts.parent | |
15 | + // RENAME agent | |
16 | + this._vm = (opts.codeHandler || codeHandler).init(state.value) | |
17 | + this._messageQueue = new MessageQueue(this) | |
18 | + this._instanceCache = new Map() | |
13 | 19 | } |
14 | 20 | |
21 | + static get PARENT () { | |
22 | + return PARENT_SYMBOL | |
23 | + } | |
24 | + | |
25 | + static get ROOT () { | |
26 | + return ROOT_SYMBOL | |
27 | + } | |
28 | + | |
29 | + get PARENT () { | |
30 | + return PARENT_SYMBOL | |
31 | + } | |
32 | + | |
33 | + get ROOT () { | |
34 | + return ROOT_SYMBOL | |
35 | + } | |
36 | + | |
15 | 37 | /** |
16 | 38 | * run the kernels code with a given enviroment |
17 | 39 | * The Kernel Stores all of its state in the Environment. The Interface is used |
18 | 40 | * to by the VM to retrive infromation from the Environment. |
19 | 41 | */ |
20 | - async run (environment = new Environment({state: this}), interfaces = this.interfaces) { | |
21 | - /** | |
22 | - * Builds a import map with an array of given interfaces | |
23 | - */ | |
24 | - async function buildImports (kernelApi, imports, state) { | |
25 | - const result = {} | |
26 | - for (const Import of imports) { | |
27 | - const newIterface = new Import(kernelApi) | |
28 | - result[Import.name] = newIterface.exports | |
29 | - // initailize the import | |
30 | - await newIterface.initialize(state) | |
31 | - } | |
42 | + async run (message, imports = this.imports) { | |
43 | + const state = this.state.copy() | |
44 | + | |
45 | + // const stateInterface = new StateInterface(state) | |
46 | + const result = await this._vm.run(message, this, imports) | |
47 | + if (!result.execption) { | |
48 | + // update the state | |
49 | + await this.state.set([], state) | |
50 | + } | |
51 | + return result | |
52 | + } | |
53 | + | |
54 | + async recieve (message) { | |
55 | + if (message.isCyclic(this)) { | |
56 | + const result = await this.run(message) | |
57 | + message.finished() | |
32 | 58 | return result |
59 | + } else { | |
60 | + return this._messageQueue.add(message) | |
33 | 61 | } |
34 | - | |
35 | - const initializedImports = await buildImports(this._vm, interfaces, this.state) | |
36 | - return await this._vm.run(environment, initializedImports) | |
37 | 62 | } |
38 | 63 | |
39 | - async messageReceiver (message) { | |
40 | - // let the code handle the message if there is code | |
41 | - const environment = new Environment({ | |
42 | - message: message | |
43 | - }) | |
44 | - let result = await this.run(environment) | |
45 | - if (!result.execption) { | |
46 | - this.state = result.state | |
64 | + async send (port, message) { | |
65 | + message.sending(this, this._messageQueue.currentMessage) | |
66 | + // replace root with parent path to root | |
67 | + if (port === ROOT_SYMBOL) { | |
68 | + port = PARENT_SYMBOL | |
69 | + message.to = new Array(this.state.path.length - 1).fill(PARENT_SYMBOL).concat(message.to) | |
47 | 70 | } |
71 | + | |
72 | + if (port === PARENT_SYMBOL) { | |
73 | + message.from.push(this.state.name) | |
74 | + } else { | |
75 | + message.from.push(this.PARENT) | |
76 | + } | |
77 | + | |
78 | + const dest = await this.getPort(port) | |
79 | + return dest.recieve(message) | |
48 | 80 | } |
49 | 81 | |
50 | - copy () { | |
51 | - return new Kernel({ | |
52 | - state: this.state.copy(), | |
53 | - code: this.code, | |
54 | - interfaces: this.interfaces, | |
55 | - parent: this.parent | |
56 | - }) | |
82 | + async getPort (port) { | |
83 | + if (this._instanceCache.has(port)) { | |
84 | + return this._instanceCache.get(port) | |
85 | + } else { | |
86 | + const destState = await (port === this.PARENT | |
87 | + ? this.state.getParent() | |
88 | + : this.state.get([port])) | |
89 | + | |
90 | + const kernel = new Kernel({ | |
91 | + state: destState | |
92 | + }) | |
93 | + this._instanceCache.set(port, kernel) | |
94 | + return kernel | |
95 | + } | |
57 | 96 | } |
58 | 97 | } |
package.json | ||
---|---|---|
@@ -39,8 +39,9 @@ | ||
39 | 39 | "dependencies": { |
40 | 40 | "bn.js": "^4.11.6", |
41 | 41 | "ethereumjs-block": "^1.2.2", |
42 | 42 | "ethereumjs-tx": "^1.1.2", |
43 | + "ethereumjs-util": "^5.0.0", | |
43 | 44 | "merkle-trie": "0.1.2", |
44 | - "ethereumjs-util": "^5.0.0" | |
45 | + "promise-queue": "^2.2.3" | |
45 | 46 | } |
46 | 47 | } |
tests/interface/address.json | ||
---|---|---|
@@ -8,6 +8,7 @@ | ||
8 | 8 | "nonce": "0x00" |
9 | 9 | } |
10 | 10 | }, |
11 | 11 | "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", |
12 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | 13 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" |
13 | 14 | } |
tests/interface/balance.json | ||
---|---|---|
@@ -8,6 +8,7 @@ | ||
8 | 8 | "nonce": "0x00" |
9 | 9 | } |
10 | 10 | }, |
11 | 11 | "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", |
12 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | 13 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" |
13 | 14 | } |
tests/interface/basic_gas_ops.json | ||
---|---|---|
@@ -8,7 +8,8 @@ | ||
8 | 8 | "nonce": "0x00" |
9 | 9 | } |
10 | 10 | }, |
11 | 11 | "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", |
12 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | 13 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", |
13 | 14 | "gasLeft": 1000 |
14 | 15 | } |
tests/interface/call.json | ||
---|---|---|
@@ -8,6 +8,7 @@ | ||
8 | 8 | "nonce": "0x00" |
9 | 9 | } |
10 | 10 | }, |
11 | 11 | "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", |
12 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | 13 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" |
13 | 14 | } |
tests/interface/callDataCopy.json | ||
---|---|---|
@@ -8,6 +8,7 @@ | ||
8 | 8 | "nonce": "0x00" |
9 | 9 | } |
10 | 10 | }, |
11 | 11 | "coinbase": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", |
12 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
12 | 13 | "address": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b" |
13 | 14 | } |
tests/interface/callDataSize.json | ||
---|---|---|
@@ -1,5 +1,6 @@ | ||
1 | 1 | { |
2 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 3 | "callValue": "0x00", |
3 | 4 | "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721", |
4 | 5 | "state": { |
5 | 6 | "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { |
tests/interface/callValue.json | ||
---|---|---|
@@ -1,5 +1,6 @@ | ||
1 | 1 | { |
2 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 3 | "callValue": "0x056bc75e2d63100000", |
3 | 4 | "callData": "0x596f75206172652077616974696e6720666f7220746865207265766f6c7574696f6e3f204c657420697420626521204d79206f776e20626567616e2061206c6f6e672074696d652061676f21205768656e20796f752077696c6c2062652072656164792e2e2e4920776f6ee2809974206d696e6420676f696e6720616c6f6e67207769746820796f7520666f722061207768696c652e20427574207768656e20796f75e280996c6c2073746f702c2049207368616c6c20636f6e74696e7565206f6e206d7920696e73616e6520616e6420747269756d7068616e742077617920746f776172642074686520677265617420616e64207375626c696d6520636f6e7175657374206f6620746865206e6f7468696e6721", |
4 | 5 | "state": { |
5 | 6 | "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { |
tests/interface/coinbase.json | ||
---|---|---|
@@ -1,5 +1,6 @@ | ||
1 | 1 | { |
2 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 3 | "callValue": "0x00", |
3 | 4 | "callData": "0x00", |
4 | 5 | "state": { |
5 | 6 | "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { |
tests/interface/origin.json | ||
---|---|---|
@@ -1,5 +1,6 @@ | ||
1 | 1 | { |
2 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 3 | "callValue": "0x00", |
3 | 4 | "callData": "0x00", |
4 | 5 | "state": { |
5 | 6 | "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { |
tests/interface/sstore.json | ||
---|---|---|
@@ -1,5 +1,6 @@ | ||
1 | 1 | { |
2 | + "caller": "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b", | |
2 | 3 | "callValue": "0x00", |
3 | 4 | "callData": "0x00", |
4 | 5 | "state": { |
5 | 6 | "0x5d48c1018904a172886829bbbd9c6f4a2d06c47b": { |
tests/interfaceRunner.js | ||
---|---|---|
@@ -2,17 +2,19 @@ | ||
2 | 2 | const fs = require('fs') |
3 | 3 | const path = require('path') |
4 | 4 | const Vertex = require('merkle-trie') |
5 | 5 | const Address = require('../deps/address') |
6 | +const Block = require('../deps/block') | |
6 | 7 | const U256 = require('../deps/u256') |
7 | - | |
8 | +// TODO remove fakeblockchain | |
9 | +const fakeBlockChain = require('../fakeBlockChain.js') | |
8 | 10 | const Kernel = require('../index.js') |
9 | -const Environment = require('../testEnvironment.js') | |
11 | +const Message = require('../message.js') | |
10 | 12 | |
11 | 13 | const dir = path.join(__dirname, '/interface') |
12 | 14 | // get the test names |
13 | 15 | let tests = fs.readdirSync(dir).filter((file) => file.endsWith('.wast')) |
14 | -// tests = ['callDataSize.wast'] | |
16 | +// tests = ['sstore.wast'] | |
15 | 17 | |
16 | 18 | runTests(tests) |
17 | 19 | |
18 | 20 | function runTests (tests) { |
@@ -23,20 +25,14 @@ | ||
23 | 25 | const rootVertex = new Vertex() |
24 | 26 | const code = fs.readFileSync(`${dir}/${testName}.wasm`) |
25 | 27 | const envData = JSON.parse(fs.readFileSync(`${dir}/${testName}.json`).toString()) |
26 | 28 | |
27 | - envData.caller = new Address(envData.caller) | |
28 | - envData.address = new Address(envData.address) | |
29 | - envData.origin = new Address(envData.origin) | |
30 | - envData.callData = new Buffer(envData.callData.slice(2), 'hex') | |
31 | - envData.callValue = new U256(envData.callValue) | |
32 | - | |
33 | 29 | for (let address in envData.state) { |
34 | 30 | const account = envData.state[address] |
35 | 31 | const accountVertex = new Vertex() |
36 | 32 | |
37 | 33 | accountVertex.set('code', new Vertex({ |
38 | - value: new Buffer(account.code.slice(2), 'hex') | |
34 | + value: code | |
39 | 35 | })) |
40 | 36 | |
41 | 37 | accountVertex.set('balance', new Vertex({ |
42 | 38 | value: new Buffer(account.balance.slice(2), 'hex') |
@@ -47,35 +43,41 @@ | ||
47 | 43 | value: new Buffer(account.storage[key].slice(2), 'hex') |
48 | 44 | })) |
49 | 45 | } |
50 | 46 | |
51 | - const path = ['accounts', ...new Buffer(address.slice(2), 'hex')] | |
47 | + const path = ['accounts', address] | |
52 | 48 | rootVertex.set(path, accountVertex) |
53 | 49 | } |
54 | 50 | |
55 | - const state = envData.state = await rootVertex.get(['accounts', ...envData.address.toBuffer()]) | |
56 | - state.value = code | |
51 | + const block = new Block() | |
52 | + block.header.coinbase = new Address(envData.coinbase) | |
57 | 53 | |
58 | - const env = new Environment(envData) | |
59 | - env.block.header.coinbase = new Address(envData.coinbase) | |
60 | - | |
61 | 54 | rootVertex.set('block', new Vertex({ |
62 | - value: env.block | |
55 | + value: block | |
63 | 56 | })) |
64 | 57 | |
65 | - const kernel = new Kernel({ | |
66 | - state: state | |
67 | - }) | |
58 | + rootVertex.set('blockchain', new Vertex({ | |
59 | + value: fakeBlockChain | |
60 | + })) | |
68 | 61 | |
62 | + const message = new Message() | |
63 | + // message.from = new Address() | |
64 | + message.to = ['accounts', envData.address, 'code'] | |
65 | + message.origin = new Address(envData.origin) | |
66 | + message.data = new Buffer(envData.callData.slice(2), 'hex') | |
67 | + message.value = new U256(envData.callValue) | |
68 | + message.gas = 1000000 | |
69 | + | |
70 | + const callerState = await rootVertex.get(['accounts', envData.caller, 'code']) | |
71 | + const caller = new Kernel({state: callerState}) | |
69 | 72 | try { |
70 | - await kernel.run(env) | |
73 | + await caller.send(Kernel.ROOT, message) | |
71 | 74 | } catch (e) { |
72 | 75 | t.fail('Exception: ' + e) |
73 | 76 | console.error('FAIL') |
74 | 77 | console.error(e) |
75 | 78 | } finally { |
76 | 79 | t.pass(testName) |
77 | - console.log('done') | |
78 | 80 | } |
79 | 81 | t.end() |
80 | 82 | }) |
81 | 83 | } |
vm.js | ||
---|---|---|
@@ -5,23 +5,53 @@ | ||
5 | 5 | */ |
6 | 6 | constructor (code) { |
7 | 7 | this._module = WebAssembly.Module(code) |
8 | 8 | } |
9 | - | |
10 | 9 | /** |
11 | 10 | * Runs the core VM with a given environment and imports |
12 | 11 | */ |
13 | - async run (environment, imports) { | |
14 | - this._environment = environment | |
15 | - // TODO, delete the instance once done. | |
16 | - const instance = this._instance = WebAssembly.Instance(this._module, imports) | |
12 | + async run (message, kernel, imports) { | |
13 | + const responses = {} | |
14 | + /** | |
15 | + * Builds a import map with an array of given interfaces | |
16 | + */ | |
17 | + async function buildImports (kernelApi, kernel, imports) { | |
18 | + const result = {} | |
19 | + for (const Import of imports) { | |
20 | + const response = responses[Import.name] = {} | |
21 | + const newIterface = new Import(kernelApi, message, response) | |
22 | + result[Import.name] = newIterface.exports | |
23 | + // initailize the import | |
24 | + await newIterface.initialize() | |
25 | + } | |
26 | + return result | |
27 | + } | |
28 | + | |
29 | + let instance | |
30 | + const kernelApi = { | |
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[callbackIndex.toString()](result) | |
38 | + }) | |
39 | + }, | |
40 | + memory: () => { | |
41 | + return instance.exports.memory.buffer | |
42 | + }, | |
43 | + kernel: kernel | |
44 | + } | |
45 | + | |
46 | + const initializedImports = await buildImports(kernelApi, kernel, imports) | |
47 | + instance = WebAssembly.Instance(this._module, initializedImports) | |
48 | + | |
17 | 49 | if (instance.exports.main) { |
18 | 50 | instance.exports.main() |
19 | 51 | } |
20 | 52 | await this.onDone() |
21 | - const env = this._environment | |
22 | - delete this._environment | |
23 | - return env | |
53 | + return responses | |
24 | 54 | } |
25 | 55 | |
26 | 56 | /** |
27 | 57 | * returns a promise that resolves when the wasm instance is done running |
@@ -33,26 +63,7 @@ | ||
33 | 63 | await this._opsQueue |
34 | 64 | } |
35 | 65 | } |
36 | 66 | |
37 | - /** | |
38 | - * addes an aync operation to the operations queue | |
39 | - */ | |
40 | - pushOpsQueue (promise, callbackIndex, intefaceCallback) { | |
41 | - this._opsQueue = Promise.all([this._opsQueue, promise]).then(values => { | |
42 | - const result = intefaceCallback(values.pop()) | |
43 | - this._instance.exports[callbackIndex.toString()](result) | |
44 | - }) | |
45 | - } | |
46 | - | |
47 | 67 | sendMessage (message) { |
48 | - | |
49 | 68 | } |
50 | - | |
51 | - get environment () { | |
52 | - return this._environment | |
53 | - } | |
54 | - | |
55 | - get memory () { | |
56 | - return this._instance.exports.memory.buffer | |
57 | - } | |
58 | 69 | } |
codeHandler.js | ||
---|---|---|
@@ -1,0 +1,32 @@ | ||
1 | +const Wasm = require('./vm.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 | + return code && code.slice(0, 4).toString() === '\x00asm' | |
15 | + }, | |
16 | + init: (code) => { | |
17 | + return new Wasm(code) | |
18 | + } | |
19 | +} | |
20 | + | |
21 | +let codeHandlers = exports.codeHandlers = [ | |
22 | + defaultHandler, | |
23 | + wasm | |
24 | +] | |
25 | + | |
26 | +exports.init = (code) => { | |
27 | + for (let handler of codeHandlers) { | |
28 | + if (handler.test(code)) { | |
29 | + return handler.init(code) | |
30 | + } | |
31 | + } | |
32 | +} |
defaultAgent.js | ||
---|---|---|
@@ -1,0 +1,8 @@ | ||
1 | +exports.run = async (message, kernel) => { | |
2 | + const to = message.nextPort() | |
3 | + if (message.toPort) { | |
4 | + return kernel.send(to, message) | |
5 | + } else if (message.data.getValue) { | |
6 | + return (await kernel.state.get(message.data.getValue)).value | |
7 | + } | |
8 | +} |
environment.js | ||
---|---|---|
@@ -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 | -} |
message.js | ||
---|---|---|
@@ -1,0 +1,45 @@ | ||
1 | +const U256 = require('./deps/u256.js') | |
2 | +const Address = require('./deps/address.js') | |
3 | + | |
4 | +module.exports = class Message { | |
5 | + constructor (opts = {}) { | |
6 | + const defaults = { | |
7 | + // call infromation | |
8 | + to: [], | |
9 | + origin: new Address('0x0000000000000000000000000000000000000000'), | |
10 | + from: [], | |
11 | + data: new Uint8Array(), | |
12 | + sync: true, | |
13 | + // resource info | |
14 | + gas: new U256(0), | |
15 | + gasPrices: new U256(0) | |
16 | + } | |
17 | + Object.assign(this, defaults, opts) | |
18 | + this._index = 0 | |
19 | + this._parentProcesses = [] | |
20 | + } | |
21 | + | |
22 | + nextPort () { | |
23 | + // this.from.push(message.toPort) | |
24 | + this.toPort = this.to[this._index] | |
25 | + this._index++ | |
26 | + return this.toPort | |
27 | + } | |
28 | + | |
29 | + finished () { | |
30 | + if (this.sync) { | |
31 | + this._parentProcesses.pop() | |
32 | + } | |
33 | + } | |
34 | + | |
35 | + sending (kernel, parentMessage) { | |
36 | + if (this.sync && parentMessage) { | |
37 | + this._parentProcesses = parentMessage._parentProcesses | |
38 | + this._parentProcesses.push(kernel) | |
39 | + } | |
40 | + } | |
41 | + | |
42 | + isCyclic (kernel) { | |
43 | + return this.sync && this._parentProcesses.some(process => process === kernel) | |
44 | + } | |
45 | +} |
Built with git-ssb-web