git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 3e76d33a7cef629ec34b59cf2d48fb0561fe2bc7

Files: 3e76d33a7cef629ec34b59cf2d48fb0561fe2bc7 / index.js

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

Built with git-ssb-web