Commit c2c2c8129841ee7fab70e39ad49ebafd8c2a4995
Merge pull request #18 from ewasm/vm-phase2
VM phase2Alex 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.js | changed |
index.js | changed |
precompile.js | added |
test.js | added |
utils.js | added |
environment.js | ||
---|---|---|
@@ -32,14 +32,24 @@ | ||
32 | 32 | |
33 | 33 | Object.assign(this, defaults, data || {}) |
34 | 34 | } |
35 | 35 | |
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 | + | |
36 | 46 | getBalance (address) { |
37 | - return this.state.get(address.toString()).balance | |
47 | + return this.state.get(address.toString())['balance'] | |
38 | 48 | } |
39 | 49 | |
40 | 50 | getCode (address) { |
41 | - return this.state.get(address.toString()).code | |
51 | + return this.state.get(address.toString())['code'] | |
42 | 52 | } |
43 | 53 | |
44 | 54 | getBlockHash (height) { |
45 | 55 | return blockChain.getBlock(height).hash() |
index.js | ||
---|---|---|
@@ -20,16 +20,27 @@ | ||
20 | 20 | const Environment = require('./environment.js') |
21 | 21 | |
22 | 22 | const DebugInterface = require('./debugInterface.js') |
23 | 23 | |
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 | + | |
24 | 33 | module.exports = class Kernel { |
25 | 34 | // runs some code in the VM |
26 | 35 | constructor (environment = new Environment()) { |
27 | 36 | this.environment = environment |
28 | 37 | } |
29 | 38 | |
30 | 39 | // 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())) { | |
32 | 43 | const debugInterface = new DebugInterface(ethInterface.environment) |
33 | 44 | |
34 | 45 | const instance = Wasm.instantiateModule(code, { |
35 | 46 | 'ethereum': ethInterface.exportTable, |
@@ -56,20 +67,154 @@ | ||
56 | 67 | // loads code from the merkle trie and delegates the message |
57 | 68 | // Detects if code is EVM or WASM |
58 | 69 | // Detects if the code injection is needed |
59 | 70 | // 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 | + | |
61 | 97 | // 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 | + } | |
66 | 127 | } |
67 | 128 | |
68 | 129 | // run tx; the tx message handler |
69 | 130 | runTx (tx, environment = new Environment()) { |
70 | 131 | // 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 | + } | |
72 | 217 | } |
73 | 218 | |
74 | 219 | // run block; the block message handler |
75 | 220 | runBlock (block, environment = new Environment()) { |
precompile.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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