git ssb

0+

wanderer🌟 / js-primea-hypervisor



Commit c2c2c8129841ee7fab70e39ad49ebafd8c2a4995

Merge pull request #18 from ewasm/vm-phase2

VM phase2
Alex Beregszaszi authored on 8/23/2016, 12:32:10 AM
GitHub committed on 8/23/2016, 12:32:10 AM
Parent: 8666afc1dd3be42258266197d74744366f739564
Parent: fdeda2b3f112344d1e935f83556be5203991411e

Files changed

environment.jschanged
index.jschanged
precompile.jsadded
test.jsadded
utils.jsadded
environment.jsView
@@ -32,14 +32,24 @@
3232
3333 Object.assign(this, defaults, data || {})
3434 }
3535
36+ addAccount (address, trie) {
37+ let account = new Map()
38+ account.set('nonce', trie.nonce || new U256(0))
39+ account.set('balance', trie.balance || new U256(0))
40+ account.set('code', trie.code || new Uint8Array())
41+ account.set('storage', trie.storage || new Map())
42+
43+ this.state.set(address.toString(), account)
44+ }
45+
3646 getBalance (address) {
37- return this.state.get(address.toString()).balance
47+ return this.state.get(address.toString())['balance']
3848 }
3949
4050 getCode (address) {
41- return this.state.get(address.toString()).code
51+ return this.state.get(address.toString())['code']
4252 }
4353
4454 getBlockHash (height) {
4555 return blockChain.getBlock(height).hash()
index.jsView
@@ -20,16 +20,27 @@
2020 const Environment = require('./environment.js')
2121
2222 const DebugInterface = require('./debugInterface.js')
2323
24+const Address = require('./address.js')
25+const U256 = require('./u256.js')
26+const Utils = require('./utils.js')
27+const Transaction = require('./transaction.js')
28+const Precompile = require('./precompile.js')
29+
30+const meteringContract = new Address("0x000000000000000000000000000000000000000A")
31+const transcompilerContract = new Address("0x000000000000000000000000000000000000000B")
32+
2433 module.exports = class Kernel {
2534 // runs some code in the VM
2635 constructor (environment = new Environment()) {
2736 this.environment = environment
2837 }
2938
3039 // handles running code.
31- static codeHandler (code, ethInterface = new Interface(new Environment())) {
40+ // NOTE: it assumes that wasm will raise an exception if something went wrong,
41+ // otherwise execution succeeded
42+ codeHandler (code, ethInterface = new Interface(new Environment())) {
3243 const debugInterface = new DebugInterface(ethInterface.environment)
3344
3445 const instance = Wasm.instantiateModule(code, {
3546 'ethereum': ethInterface.exportTable,
@@ -56,20 +67,154 @@
5667 // loads code from the merkle trie and delegates the message
5768 // Detects if code is EVM or WASM
5869 // Detects if the code injection is needed
5970 // Detects if transcompilation is needed
60- static callHandler (path, data) {
71+ callHandler (call) {
72+ // FIXME: this is here until these two contracts are compiled to WASM
73+ // The two special contracts (precompiles now, but will be real ones later)
74+ if (call.to.equals(meteringContract)) {
75+ return Precompile.meteringInjector(call)
76+ } else if (call.to.equals(transcompilerContract)) {
77+ return Precompile.transcompiler(call)
78+ }
79+
80+ let account = this.environment.state.get(call.to.toString())
81+ if (!account) {
82+ throw new Error('Account not found: ' + call.to.toString())
83+ }
84+
85+ const code = Uint8Array.from(account.get('code'))
86+ if (code.length === 0) {
87+ throw new Error('Contract not found')
88+ }
89+
90+ if (!Utils.isWASMCode(code)) {
91+ // throw new Error('Not an eWASM contract')
92+
93+ // Transcompile code
94+ code = this.callHandler({ to: transcompilerContract, data: code }).returnValue
95+ }
96+
6197 // creats a new Kernel
62- // const environment = new Environment(data)
63- // environment.parent = this
64- // const kernel = new Kernel(this, environment)
65- // kernel.codeHandler(code)
98+ const environment = new Environment()
99+ environment.parent = this
100+
101+ // copy the transaction details
102+ environment.code = code
103+ environment.address = call.to
104+ // FIXME: make distinction between origin and caller
105+ environment.origin = call.from
106+ environment.caller = call.from
107+ environment.callData = call.data
108+ environment.callValue = call.value
109+ environment.gasLeft = call.gasLimit
110+
111+ //environment.setCallHandler(callHandler)
112+
113+ const kernel = new Kernel(this, environment)
114+ kernel.codeHandler(code, new Interface(environment))
115+
116+ // generate new stateroot
117+ //this.environment.state.set(address, { stateRoot: stateRoot })
118+
119+ return {
120+ executionOutcome: 1, // success
121+ gasLeft: new U256(environment.gasLeft),
122+ gasRefund: new U256(environment.gasRefund),
123+ returnValue: environment.returnValue,
124+ selfDestructAddress: environment.selfDestructAddress,
125+ logs: environment.logs
126+ }
66127 }
67128
68129 // run tx; the tx message handler
69130 runTx (tx, environment = new Environment()) {
70131 // verify tx then send to call Handler
71- this.callHandler(tx, environment)
132+ // - from account has enough balance
133+ // - check nonce
134+ // - ecrecover
135+ // new ethTx(tx).validate(tx)
136+ // - reduce balance
137+
138+ this.environment = environment
139+
140+ //
141+ // environment.state - the merkle tree
142+ // key: address (20 byte, hex string, without 0x prefix)
143+ // every path has an account
144+ //
145+ // { balance, codeHash, stateRoot }
146+ //
147+
148+ if (Buffer.isBuffer(tx) || typeof tx === 'string') {
149+ tx = new Transaction(tx)
150+ if (!tx.valid) {
151+ throw new Error('Invalid transaction signature')
152+ }
153+ }
154+
155+ // look up sender
156+ let fromAccount = this.environment.state.get(tx.from.toString())
157+ if (!fromAccount) {
158+ throw new Error('Sender account not found: ' + tx.from.toString())
159+ }
160+
161+ if (fromAccount.get('nonce').gt(tx.nonce)) {
162+ throw new Error(`Invalid nonce: ${fromAccount.get('nonce')} > ${tx.nonce}`)
163+ }
164+
165+ fromAccount.set('nonce', fromAccount.get('nonce').add(new U256(1)))
166+
167+ // Special case: contract deployment
168+ if (tx.to.isZero()) {
169+ if (tx.data.length !== 0) {
170+ console.log('This is a contract deployment transaction')
171+
172+ // Inject metering
173+ const code = this.callHandler({ to: meteringContract, data: tx.data }).returnValue
174+
175+ let address = Utils.newAccountAddress(tx.from, code)
176+
177+ this.environment.addAccount(address.toString(), {
178+ balance: tx.value,
179+ code: code
180+ })
181+
182+ // FIXME: deduct fees
183+
184+ return {
185+ accountCreated: address
186+ }
187+ }
188+ }
189+
190+ // deduct gasLimit * gasPrice from sender
191+ if (fromAccount.get('balance').lt(tx.gasLimit.mul(tx.gasPrice))) {
192+ throw new Error(`Insufficient account balance: ${fromAccount.get('balance').toString()} < ${tx.gasLimit.mul(tx.gasPrice).toString()}`)
193+ }
194+
195+ fromAccount.set('balance', fromAccount.get('balance').sub(tx.gasLimit.mul(tx.gasPrice)))
196+
197+ let ret = this.callHandler({
198+ to: tx.to,
199+ from: tx.from,
200+ gasLimit: tx.gasLimit,
201+ value: tx.value,
202+ data: tx.data
203+ })
204+
205+ // refund gas
206+ if (ret.executionOutcome === 1) {
207+ fromAccount.set('balance', fromAccount.get('balance').add(tx.gasPrice.mul(ret.gasLeft.add(ret.gasRefund))))
208+ }
209+
210+ // save new state?
211+
212+ return {
213+ returnValue: ret.returnValue,
214+ gasLeft: ret.gasLeft,
215+ logs: ret.logs
216+ }
72217 }
73218
74219 // run block; the block message handler
75220 runBlock (block, environment = new Environment()) {
precompile.jsView
@@ -1,0 +1,13 @@
1+module.exports.meteringInjector = function (call) {
2+ console.log('Executing metering injector')
3+ return {
4+ returnValue: call.data.slice(0)
5+ }
6+}
7+
8+module.exports.transcompiler = function (call) {
9+ console.log('Executing transcompiler')
10+ return {
11+ returnValue: call.data.slice(0)
12+ }
13+}
test.jsView
@@ -1,0 +1,41 @@
1+const Kernel = require('./index.js')
2+const Environment = require('./environment.js')
3+
4+const environment = new Environment()
5+const kernel = new Kernel(environment)
6+
7+const Address = require('./address.js')
8+const U256 = require('./u256.js')
9+
10+const fs = require('fs')
11+
12+environment.addAccount(new Address('0x1234567890134561111123412341234123412341'), { balance: new U256('100000000000000000') })
13+//environment.addAccount(new Address('0x4123412341234123411234567890134561111123'), { code: Uint8Array.from(fs.readFileSync('identity.wasm')) })
14+
15+environment.addAccount(new Address('0xbe862ad9abfe6f22bcb087716c7d89a26051f74c'), { balance: new U256('100000000000000000') })
16+var tx = new Buffer('f8e380648203e88080b8977f4e616d65526567000000000000000000000000000000000000000000000000003057307f4e616d6552656700000000000000000000000000000000000000000000000000573360455760415160566000396000f20036602259604556330e0f600f5933ff33560f601e5960003356576000335700604158600035560f602b590033560f603659600033565733600035576000353357001ca0b8b9fedc076110cd002224a942e9d7099e4a626ebf66cd9301fc18e2c1181806a04e270be511d42189baf14599eb8d6eb5037ab105032dd3e0fa05b43dad4cb4c2', 'hex')
17+console.log(kernel.runTx(tx, environment))
18+
19+// deploy contract
20+let ret = kernel.runTx({
21+ nonce: new U256(3),
22+ from: new Address('0x1234567890134561111123412341234123412341'),
23+ to: new Address('0x0000000000000000000000000000000000000000'),
24+ value: new U256('100'),
25+ gasLimit: new U256('1000000000'),
26+ gasPrice: new U256(1),
27+ data: Uint8Array.from(fs.readFileSync('identity.wasm'))
28+}, environment)
29+console.log('Account created: ' + ret.accountCreated)
30+
31+ret = kernel.runTx({
32+ nonce: new U256(4),
33+ from: new Address('0x1234567890134561111123412341234123412341'),
34+ to: ret.accountCreated, //new Address('0x4123412341234123411234567890134561111123'),
35+ value: new U256('100'),
36+ gasLimit: new U256('1000000000'),
37+ gasPrice: new U256(1),
38+ data: Uint8Array.from(new Buffer('spartaaaa'))
39+}, environment)
40+
41+console.log('Return value: ' + ret.returnValue)
utils.jsView
@@ -1,0 +1,14 @@
1+const ethUtils = require('ethereumjs-util')
2+const Address = require('./address.js')
3+
4+var Utils = {}
5+
6+Utils.isWASMCode = function (code) {
7+ return code.slice(0, 4).toString() === new Uint8Array([0, 0x61, 0x73, 0x6d]).toString()
8+}
9+
10+Utils.newAccountAddress = function (sender, data) {
11+ return new Address('0x' + ethUtils.sha3(Buffer.concat([ sender.toBuffer(), Buffer.from(data) ])).slice(0, 20).toString('hex'))
12+}
13+
14+module.exports = Utils

Built with git-ssb-web