git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: fdeda2b3f112344d1e935f83556be5203991411e

Files: fdeda2b3f112344d1e935f83556be5203991411e / index.js

7190 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')
21
22const DebugInterface = require('./debugInterface.js')
23
24const Address = require('./address.js')
25const U256 = require('./u256.js')
26const Utils = require('./utils.js')
27const Transaction = require('./transaction.js')
28const Precompile = require('./precompile.js')
29
30const meteringContract = new Address("0x000000000000000000000000000000000000000A")
31const transcompilerContract = new Address("0x000000000000000000000000000000000000000B")
32
33module.exports = class Kernel {
34 // runs some code in the VM
35 constructor (environment = new Environment()) {
36 this.environment = environment
37 }
38
39 // handles running code.
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())) {
43 const debugInterface = new DebugInterface(ethInterface.environment)
44
45 const instance = Wasm.instantiateModule(code, {
46 'ethereum': ethInterface.exportTable,
47 'debug': debugInterface.exportTable,
48
49 // export this for Rust
50 // FIXME: remove once Rust has proper imports, see https://github.com/ethereum/evm2.0-design/issues/15
51 'spectest': ethInterface.exportTable,
52
53 // export this for Binaryen
54 // FIXME: remove once C has proper imports, see https://github.com/ethereum/evm2.0-design/issues/16
55 'env': ethInterface.exportTable
56 })
57
58 ethInterface.setModule(instance)
59 debugInterface.setModule(instance)
60
61 if (instance.exports.main) {
62 instance.exports.main()
63 }
64 return instance
65 }
66
67 // loads code from the merkle trie and delegates the message
68 // Detects if code is EVM or WASM
69 // Detects if the code injection is needed
70 // Detects if transcompilation is needed
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
97 // creats a new Kernel
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 }
127 }
128
129 // run tx; the tx message handler
130 runTx (tx, environment = new Environment()) {
131 // verify tx then send to call Handler
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 }
217 }
218
219 // run block; the block message handler
220 runBlock (block, environment = new Environment()) {
221 // verify block then run each tx
222 block.tx.forEach((tx) => {
223 this.runTx(tx, environment)
224 })
225 }
226
227 // run blockchain
228 // runBlockchain () {}
229}
230

Built with git-ssb-web