git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: ed1a622c86d302461688dd47df71585c8e5e74e4

Files: ed1a622c86d302461688dd47df71585c8e5e74e4 / index.js

10295 bytesRaw
1/**
2 * This implements the Ethereum Kernel
3 * Kernels must implement two methods `codeHandler` and `callHandler` (and `linkHandler` for sharding)
4 * The Kernel Contract handles the following
5 * - Interprocess communications
6 * - Intializing the VM and exposes ROM to it (codeHandler)
7 * - Expose namespace which VM instance exists and Intializes the Environment (callHandler)
8 * - Provides some built in contract (runTx, runBlock)
9 * - Provides resource sharing and limiting via gas
10 *
11 * All State should be stored in the Environment.
12 *
13 */
14
15// The Kernel Exposes this Interface to VM instances it makes
16const Interface = require('./interface.js')
17
18// The Kernel Stores all of its state in the Environment. The Interface is used
19// to by the VM to retrive infromation from the Environment.
20const Environment = require('./environment.js')
21const DebugInterface = require('./debugInterface.js')
22const Address = require('./deps/address.js')
23const U256 = require('./deps/u256.js')
24const Utils = require('./deps/utils.js')
25const Transaction = require('./deps/transaction.js')
26const Precompile = require('./precompile.js')
27
28const identityContract = new Address('0x0000000000000000000000000000000000000004')
29const meteringContract = new Address('0x000000000000000000000000000000000000000A')
30const transcompilerContract = new Address('0x000000000000000000000000000000000000000B')
31
32module.exports = class Kernel {
33 // runs some code in the VM
34 constructor (environment = new Environment()) {
35 this.environment = environment
36 this._runningOps = Promise.resolve()
37
38 // this.environment.addAccount(identityContract, {})
39 // this.environment.addAccount(meteringContract, {})
40 // this.environment.addAccount(transcompilerContract, {})
41 }
42
43 // handles running code.
44 // NOTE: it assumes that wasm will raise an exception if something went wrong,
45 // otherwise execution succeeded
46 codeHandler (code, ethInterface = new Interface(new Environment(), this)) {
47 // TODO remove
48 ethInterface.kernel = this
49 const debugInterface = new DebugInterface(ethInterface.environment)
50 const module = WebAssembly.Module(code)
51 const imports = {
52 'ethereum': ethInterface.exportTable,
53 'debug': debugInterface.exportTable,
54
55 // export this for Rust
56 // FIXME: remove once Rust has proper imports, see https://github.com/ethereum/evm2.0-design/issues/15
57 'spectest': ethInterface.exportTable,
58
59 // export this for Binaryen
60 // FIXME: remove once C has proper imports, see https://github.com/ethereum/evm2.0-design/issues/16
61 'env': ethInterface.exportTable
62 }
63 // add shims
64 imports.ethereum.useGas = ethInterface.shims.exports.useGas
65 imports.ethereum.getGasLeft = ethInterface.shims.exports.getGasLeft
66 imports.ethereum.call = ethInterface.shims.exports.call
67
68 const instance = this.instance = WebAssembly.Instance(module, imports)
69
70 ethInterface.setModule(instance)
71 debugInterface.setModule(instance)
72
73 if (instance.exports.main) {
74 instance.exports.main()
75 }
76 return this.onDone()
77 }
78
79 // returns a promise that resolves when the wasm instance is done running
80 async onDone () {
81 let prevOps
82 while (prevOps !== this._runningOps) {
83 prevOps = this._runningOps
84 await this._runningOps
85 }
86 }
87
88 // loads code from the merkle trie and delegates the message
89 // Detects if code is EVM or WASM
90 // Detects if the code injection is needed
91 // Detects if transcompilation is needed
92 messageHandler (call) {
93 // FIXME: this is here until these two contracts are compiled to WASM
94 // The two special contracts (precompiles now, but will be real ones later)
95 if (call.to.equals(meteringContract)) {
96 return Precompile.meteringInjector(call)
97 } else if (call.to.equals(transcompilerContract)) {
98 return Precompile.transcompiler(call)
99 } else if (call.to.equals(identityContract)) {
100 return Precompile.identity(call)
101 }
102
103 let account = this.environment.state.get(call.to.toString())
104 if (!account) {
105 throw new Error('Account not found: ' + call.to.toString())
106 }
107
108 let code = Uint8Array.from(account.get('code'))
109 if (code.length === 0) {
110 throw new Error('Contract not found')
111 }
112
113 if (!Utils.isWASMCode(code)) {
114 // throw new Error('Not an eWASM contract')
115
116 // Transcompile code
117 // FIXME: decide if these are the right values here: from: 0, gasLimit: 0, value: 0
118 code = this.messageHandler({ from: Address.zero(), to: transcompilerContract, gasLimit: 0, value: new U256(0), data: code }).returnValue
119
120 if (code[0] === 0) {
121 code = code.slice(1)
122 } else {
123 throw new Error('Transcompilation failed: ' + Buffer.from(code).slice(1).toString())
124 }
125 }
126
127 // creats a new Kernel
128 const environment = new Environment()
129 environment.parent = this
130
131 // copy the transaction details
132 environment.code = code
133 environment.address = call.to
134 // FIXME: make distinction between origin and caller
135 environment.origin = call.from
136 environment.caller = call.from
137 environment.callData = call.data
138 environment.callValue = call.value
139 environment.gasLeft = call.gasLimit
140
141 environment.callHandler = this.callHandler.bind(this)
142 environment.createHandler = this.createHandler.bind(this)
143
144 const kernel = new Kernel(environment)
145 kernel.codeHandler(code, new Interface(environment))
146
147 // self destructed
148 if (environment.selfDestruct) {
149 const balance = this.state.get(call.to.toString()).get('balance')
150 const beneficiary = this.state.get(environment.selfDestructAddress)
151 beneficiary.set('balance', beneficiary.get('balance').add(balance))
152 this.state.delete(call.to.toString())
153 }
154
155 // generate new stateroot
156 // this.environment.state.set(address, { stateRoot: stateRoot })
157
158 return {
159 executionOutcome: 1, // success
160 gasLeft: new U256(environment.gasLeft),
161 gasRefund: new U256(environment.gasRefund),
162 returnValue: environment.returnValue,
163 selfDestruct: environment.selfDestruct,
164 selfDestructAddress: environment.selfDestructAddress,
165 logs: environment.logs
166 }
167 }
168
169 createHandler (create) {
170 let code = create.data
171
172 // Inject metering
173 if (Utils.isWASMCode(code)) {
174 // FIXME: decide if these are the right values here: from: 0, gasLimit: 0, value: 0
175 code = this.callHandler({ from: Address.zero(), to: meteringContract, gasLimit: 0, value: new U256(0), data: code }).returnValue
176
177 if (code[0] === 0) {
178 code = code.slice(1)
179 } else {
180 throw new Error('Metering injection failed: ' + Buffer.from(code).slice(1).toString())
181 }
182 }
183
184 let account = this.environment.state.get(create.from.toString())
185 if (!account) {
186 throw new Error('Account not found: ' + create.from.toString())
187 }
188
189 let address = Utils.newAccountAddress(create.from, account.get('nonce'))
190
191 this.environment.addAccount(address.toString(), {
192 balance: create.value,
193 code: code
194 })
195
196 // Run code and take return value as contract code
197 // FIXME: decide if these are the right values here: value: 0, data: ''
198 code = this.callHandler({ from: create.from, to: address, gasLimit: create.gasLimit, value: new U256(0), data: new Uint8Array() }).returnValue
199
200 // FIXME: special handling for selfdestruct
201
202 this.environment.state.get(address.toString()).set('code', code)
203
204 return {
205 executionOutcome: 1, // success
206 gasLeft: new U256(this.environment.gasLeft),
207 gasRefund: new U256(this.environment.gasRefund),
208 accountCreated: address,
209 logs: this.environment.logs
210 }
211 }
212
213 // run tx; the tx message handler
214 runTx (tx, environment = new Environment()) {
215 this.environment = environment
216
217 if (Buffer.isBuffer(tx) || typeof tx === 'string') {
218 tx = new Transaction(tx)
219 if (!tx.valid) {
220 throw new Error('Invalid transaction signature')
221 }
222 }
223
224 // look up sender
225 let fromAccount = this.environment.state.get(tx.from.toString())
226 if (!fromAccount) {
227 throw new Error('Sender account not found: ' + tx.from.toString())
228 }
229
230 if (fromAccount.get('nonce').gt(tx.nonce)) {
231 throw new Error(`Invalid nonce: ${fromAccount.get('nonce')} > ${tx.nonce}`)
232 }
233
234 fromAccount.set('nonce', fromAccount.get('nonce').add(new U256(1)))
235
236 let isCreation = false
237
238 // Special case: contract deployment
239 if (tx.to.isZero() && (tx.data.length !== 0)) {
240 console.log('This is a contract deployment transaction')
241 isCreation = true
242 }
243
244 // This cost will not be refunded
245 let txCost = 21000 + (isCreation ? 32000 : 0)
246 tx.data.forEach((item) => {
247 if (item === 0) {
248 txCost += 4
249 } else {
250 txCost += 68
251 }
252 })
253
254 if (tx.gasLimit.lt(new U256(txCost))) {
255 throw new Error(`Minimum transaction gas limit not met: ${txCost}`)
256 }
257
258 if (fromAccount.get('balance').lt(tx.gasLimit.mul(tx.gasPrice))) {
259 throw new Error(`Insufficient account balance: ${fromAccount.get('balance').toString()} < ${tx.gasLimit.mul(tx.gasPrice).toString()}`)
260 }
261
262 // deduct gasLimit * gasPrice from sender
263 fromAccount.set('balance', fromAccount.get('balance').sub(tx.gasLimit.mul(tx.gasPrice)))
264
265 const handler = isCreation ? this.createHandler.bind(this) : this.callHandler.bind(this)
266 let ret = handler({
267 to: tx.to,
268 from: tx.from,
269 gasLimit: tx.gasLimit - txCost,
270 value: tx.value,
271 data: tx.data
272 })
273
274 // refund unused gas
275 if (ret.executionOutcome === 1) {
276 fromAccount.set('balance', fromAccount.get('balance').add(tx.gasPrice.mul(ret.gasLeft.add(ret.gasRefund))))
277 }
278
279 // save new state?
280
281 return {
282 executionOutcome: ret.executionOutcome,
283 accountCreated: isCreation ? ret.accountCreated : undefined,
284 returnValue: isCreation ? undefined : ret.returnValue,
285 gasLeft: ret.gasLeft,
286 logs: ret.logs
287 }
288 }
289
290 // run block; the block message handler
291 runBlock (block, environment = new Environment()) {
292 // verify block then run each tx
293 block.tx.forEach((tx) => {
294 this.runTx(tx, environment)
295 })
296 }
297
298 // run blockchain
299 // runBlockchain () {}
300}
301

Built with git-ssb-web