Files: aa7f348ee71a97e479efcc2d058b302bcf683a0d / index.js
6808 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 |
16 | const 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. |
20 | const Environment = require('./environment.js') |
21 | |
22 | const DebugInterface = require('./debugInterface.js') |
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 | |
33 | module.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 | let 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 | this.environment = environment |
132 | |
133 | if (Buffer.isBuffer(tx) || typeof tx === 'string') { |
134 | tx = new Transaction(tx) |
135 | if (!tx.valid) { |
136 | throw new Error('Invalid transaction signature') |
137 | } |
138 | } |
139 | |
140 | // look up sender |
141 | let fromAccount = this.environment.state.get(tx.from.toString()) |
142 | if (!fromAccount) { |
143 | throw new Error('Sender account not found: ' + tx.from.toString()) |
144 | } |
145 | |
146 | if (fromAccount.get('nonce').gt(tx.nonce)) { |
147 | throw new Error(`Invalid nonce: ${fromAccount.get('nonce')} > ${tx.nonce}`) |
148 | } |
149 | |
150 | fromAccount.set('nonce', fromAccount.get('nonce').add(new U256(1))) |
151 | |
152 | // Special case: contract deployment |
153 | if (tx.to.isZero()) { |
154 | if (tx.data.length !== 0) { |
155 | console.log('This is a contract deployment transaction') |
156 | |
157 | // Inject metering |
158 | const code = this.callHandler({ to: meteringContract, data: tx.data }).returnValue |
159 | |
160 | let address = Utils.newAccountAddress(tx.from, code) |
161 | |
162 | this.environment.addAccount(address.toString(), { |
163 | balance: tx.value, |
164 | code: code |
165 | }) |
166 | |
167 | // FIXME: deduct fees |
168 | |
169 | return { |
170 | accountCreated: address |
171 | } |
172 | } |
173 | } |
174 | |
175 | // deduct gasLimit * gasPrice from sender |
176 | if (fromAccount.get('balance').lt(tx.gasLimit.mul(tx.gasPrice))) { |
177 | throw new Error(`Insufficient account balance: ${fromAccount.get('balance').toString()} < ${tx.gasLimit.mul(tx.gasPrice).toString()}`) |
178 | } |
179 | |
180 | fromAccount.set('balance', fromAccount.get('balance').sub(tx.gasLimit.mul(tx.gasPrice))) |
181 | |
182 | let ret = this.callHandler({ |
183 | to: tx.to, |
184 | from: tx.from, |
185 | gasLimit: tx.gasLimit, |
186 | value: tx.value, |
187 | data: tx.data |
188 | }) |
189 | |
190 | // refund gas |
191 | if (ret.executionOutcome === 1) { |
192 | fromAccount.set('balance', fromAccount.get('balance').add(tx.gasPrice.mul(ret.gasLeft.add(ret.gasRefund)))) |
193 | } |
194 | |
195 | // save new state? |
196 | |
197 | return { |
198 | returnValue: ret.returnValue, |
199 | gasLeft: ret.gasLeft, |
200 | logs: ret.logs |
201 | } |
202 | } |
203 | |
204 | // run block; the block message handler |
205 | runBlock (block, environment = new Environment()) { |
206 | // verify block then run each tx |
207 | block.tx.forEach((tx) => { |
208 | this.runTx(tx, environment) |
209 | }) |
210 | } |
211 | |
212 | // run blockchain |
213 | // runBlockchain () {} |
214 | } |
215 |
Built with git-ssb-web