git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 5bc36d61069f96790b11614e2244d309fb385b2e

Files: 5bc36d61069f96790b11614e2244d309fb385b2e / index.js

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

Built with git-ssb-web