1
+ import {
2
+ Account ,
3
+ Address ,
4
+ BN ,
5
+ bufferToHex ,
6
+ toBuffer ,
7
+ stripHexPrefix ,
8
+ unpadBuffer ,
9
+ setLengthLeft ,
10
+ } from 'ethereumjs-util'
11
+ import { Transaction } from '@ethereumjs/tx'
12
+ import { decode } from 'rlp'
1
13
import { Chain } from '../../blockchain'
2
14
import { middleware , validators } from '../validation'
3
- import { BN , bufferToHex , toBuffer , stripHexPrefix } from 'ethereumjs-util'
4
15
import { EthereumClient } from '../..'
16
+ import { INVALID_PARAMS } from '../error-code'
17
+ import { RpcCallTx } from '../types'
18
+ import type VM from '@ethereumjs/vm'
5
19
6
20
/**
7
21
* eth_* RPC module
8
22
* @memberof module:rpc/modules
9
23
*/
10
24
export class Eth {
11
25
private _chain : Chain
26
+ private _vm : VM | undefined
12
27
public ethVersion : number
13
28
14
29
/**
15
30
* Create eth_* RPC module
16
- * @param { Node } Node to which the module binds
31
+ * @param client Client to which the module binds
17
32
*/
18
- constructor ( node : EthereumClient ) {
19
- const service = node . services . find ( ( s ) => s . name === 'eth' )
20
- this . _chain = service ! . chain
21
- const ethProtocol = service ! . protocols . find ( ( p ) => p . name === 'eth' )
22
- this . ethVersion = Math . max . apply ( Math , ethProtocol ! . versions )
33
+ constructor ( client : EthereumClient ) {
34
+ const service = client . services . find ( ( s ) => s . name === 'eth' )
35
+ if ( ! service ) {
36
+ throw new Error ( 'cannot find eth service' )
37
+ }
38
+ this . _chain = service . chain
39
+ this . _vm = ( service . synchronizer as any ) ?. execution ?. vm
40
+
41
+ const ethProtocol = service . protocols . find ( ( p ) => p . name === 'eth' )
42
+ if ( ! ethProtocol ) {
43
+ throw new Error ( 'cannot find eth protocol' )
44
+ }
45
+ this . ethVersion = Math . max ( ...ethProtocol . versions )
23
46
24
47
this . blockNumber = middleware ( this . blockNumber . bind ( this ) , 0 )
25
48
49
+ this . call = middleware ( this . call . bind ( this ) , 2 , [
50
+ [ validators . transaction ( [ 'to' ] ) ] ,
51
+ [ validators . blockOption ] ,
52
+ ] )
53
+
54
+ this . estimateGas = middleware ( this . estimateGas . bind ( this ) , 2 , [
55
+ [ validators . transaction ( [ 'to' ] ) ] ,
56
+ [ validators . blockOption ] ,
57
+ ] )
58
+
59
+ this . getBalance = middleware ( this . getBalance . bind ( this ) , 2 , [
60
+ [ validators . address ] ,
61
+ [ validators . blockOption ] ,
62
+ ] )
63
+
26
64
this . getBlockByNumber = middleware ( this . getBlockByNumber . bind ( this ) , 2 , [
27
65
[ validators . hex ] ,
28
66
[ validators . bool ] ,
@@ -39,24 +77,174 @@ export class Eth {
39
77
[ [ validators . hex , validators . blockHash ] ]
40
78
)
41
79
80
+ this . getCode = middleware ( this . getCode . bind ( this ) , 2 , [
81
+ [ validators . address ] ,
82
+ [ validators . blockOption ] ,
83
+ ] )
84
+
85
+ this . getStorageAt = middleware ( this . getStorageAt . bind ( this ) , 3 , [
86
+ [ validators . address ] ,
87
+ [ validators . hex ] ,
88
+ [ validators . blockOption ] ,
89
+ ] )
90
+
91
+ this . getTransactionCount = middleware ( this . getTransactionCount . bind ( this ) , 2 , [
92
+ [ validators . address ] ,
93
+ [ validators . blockOption ] ,
94
+ ] )
95
+
42
96
this . protocolVersion = middleware ( this . protocolVersion . bind ( this ) , 0 , [ ] )
43
97
}
44
98
45
99
/**
46
100
* Returns number of the most recent block.
47
- * @param {Array<*> } [params] An empty array
48
- * @return {Promise }
101
+ * @param params An empty array
49
102
*/
50
103
async blockNumber ( _params = [ ] ) {
51
104
const latestHeader = await this . _chain . getLatestHeader ( )
52
105
return `0x${ latestHeader . number . toString ( 16 ) } `
53
106
}
54
107
55
108
/**
56
- * Returns information about a block by block number
57
- * @param {Array<BN|bool> } [params] An array of two parameters: An big integer of a block number and a
58
- * boolean determining whether it returns full transaction objects or just the transaction hashes
59
- * @return {Promise }
109
+ * Executes a new message call immediately without creating a transaction on the block chain.
110
+ * Currently only "latest" block number is supported.
111
+ * @param params An array of two parameters:
112
+ * 1. The transaction object
113
+ * * from (optional) - The address the transaction is sent from
114
+ * * to - The address the transaction is directed to
115
+ * * gas (optional) - Integer of the gas provided for the transaction execution
116
+ * * gasPrice (optional) - Integer of the gasPrice used for each paid gas
117
+ * * value (optional) - Integer of the value sent with this transaction
118
+ * * data (optional) - Hash of the method signature and encoded parameters.
119
+ * 2. integer block number, or the string "latest", "earliest" or "pending"
120
+ * @returns The return value of the executed contract.
121
+ */
122
+ async call ( params : [ RpcCallTx , string ] ) {
123
+ const [ transaction , blockOpt ] = params
124
+
125
+ const latestBlockNumber = await this . blockNumber ( )
126
+ if ( blockOpt !== 'latest' && blockOpt !== latestBlockNumber ) {
127
+ // todo: this can be resolved with some kind of functionality of stateAt(blockNumber)
128
+ return {
129
+ code : INVALID_PARAMS ,
130
+ message : `Currently only block option "latest" supported` ,
131
+ }
132
+ }
133
+
134
+ if ( ! this . _vm ) {
135
+ throw new Error ( 'missing vm' )
136
+ }
137
+
138
+ if ( ! transaction . gas ) {
139
+ // If no gas limit is specified use the last block gas limit as an upper bound.
140
+ const latestHeader = await this . _chain . getLatestHeader ( )
141
+ transaction . gas = latestHeader . gasLimit as any
142
+ }
143
+
144
+ const vm = this . _vm . copy ( )
145
+ const txData = { ...transaction , gasLimit : transaction . gas }
146
+ const tx = Transaction . fromTxData ( txData , { common : vm . _common , freeze : false } )
147
+
148
+ // set from address
149
+ const from = transaction . from ? Address . fromString ( transaction . from ) : Address . zero ( )
150
+ tx . getSenderAddress = ( ) => {
151
+ return from
152
+ }
153
+
154
+ const { execResult } = await vm . runTx ( { tx } )
155
+ return bufferToHex ( unpadBuffer ( execResult . returnValue ) )
156
+ }
157
+
158
+ /**
159
+ * Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
160
+ * The transaction will not be added to the blockchain.
161
+ * Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.
162
+ * Currently only "latest" block number is supported.
163
+ * @param params An array of two parameters:
164
+ * 1. The transaction object
165
+ * * from (optional) - The address the transaction is sent from
166
+ * * to - The address the transaction is directed to
167
+ * * gas (optional) - Integer of the gas provided for the transaction execution
168
+ * * gasPrice (optional) - Integer of the gasPrice used for each paid gas
169
+ * * value (optional) - Integer of the value sent with this transaction
170
+ * * data (optional) - Hash of the method signature and encoded parameters.
171
+ * 2. integer block number, or the string "latest", "earliest" or "pending"
172
+ * @returns The amount of gas used.
173
+ */
174
+ async estimateGas ( params : [ RpcCallTx , string ] ) {
175
+ const [ transaction , blockOpt ] = params
176
+
177
+ const latestBlockNumber = await this . blockNumber ( )
178
+ if ( blockOpt !== 'latest' && blockOpt !== latestBlockNumber ) {
179
+ // todo: this can be resolved with some kind of functionality of stateAt(blockNumber)
180
+ return {
181
+ code : INVALID_PARAMS ,
182
+ message : `Currently only block option "latest" supported` ,
183
+ }
184
+ }
185
+
186
+ if ( ! this . _vm ) {
187
+ throw new Error ( 'missing vm' )
188
+ }
189
+
190
+ if ( ! transaction . gas ) {
191
+ // If no gas limit is specified use the last block gas limit as an upper bound.
192
+ const latestHeader = await this . _chain . getLatestHeader ( )
193
+ transaction . gas = latestHeader . gasLimit as any
194
+ }
195
+
196
+ const vm = this . _vm . copy ( )
197
+ const txData = { ...transaction , gasLimit : transaction . gas }
198
+ const tx = Transaction . fromTxData ( txData , { common : vm . _common , freeze : false } )
199
+
200
+ // set from address
201
+ const from = transaction . from ? Address . fromString ( transaction . from ) : Address . zero ( )
202
+ tx . getSenderAddress = ( ) => {
203
+ return from
204
+ }
205
+
206
+ const { gasUsed } = await vm . runTx ( {
207
+ tx,
208
+ skipNonce : true ,
209
+ skipBalance : true ,
210
+ skipBlockGasLimitValidation : true ,
211
+ } )
212
+ return `0x${ gasUsed . toString ( 16 ) } `
213
+ }
214
+
215
+ /**
216
+ * Returns the balance of the account at the given address.
217
+ * Currently only "latest" block number is supported.
218
+ * @param params An array of two parameters:
219
+ * 1. address of the account
220
+ * 2. integer block number, or the string "latest", "earliest" or "pending"
221
+ */
222
+ async getBalance ( params : [ string , string ] ) {
223
+ const [ addressHex , blockOpt ] = params
224
+
225
+ const latestBlockNumber = await this . blockNumber ( )
226
+ if ( blockOpt !== 'latest' && blockOpt !== latestBlockNumber ) {
227
+ // todo: this can be resolved with some kind of functionality of stateAt(blockNumber)
228
+ return {
229
+ code : INVALID_PARAMS ,
230
+ message : `Currently only block option "latest" supported` ,
231
+ }
232
+ }
233
+
234
+ if ( ! this . _vm ) {
235
+ throw new Error ( 'missing vm' )
236
+ }
237
+
238
+ const address = Address . fromString ( addressHex )
239
+ const account : Account = await ( this . _vm . stateManager as any ) . getAccount ( address )
240
+ return `0x${ account . balance . toString ( 16 ) } `
241
+ }
242
+
243
+ /**
244
+ * Returns information about a block by block number.
245
+ * @param params An array of two parameters:
246
+ * 1. integer of a block number
247
+ * 2. boolean determining whether it returns full transaction objects or just the transaction hashes
60
248
*/
61
249
async getBlockByNumber ( params : [ string , boolean ] ) {
62
250
const [ blockNumber , includeTransactions ] = params
@@ -70,10 +258,10 @@ export class Eth {
70
258
}
71
259
72
260
/**
73
- * Returns information about a block by hash
74
- * @param { Array<string|bool> } [ params] An array of two parameters: A block hash as the first argument and a
75
- * boolean determining whether it returns full transaction objects or just the transaction hashes
76
- * @return { Promise }
261
+ * Returns information about a block by hash.
262
+ * @param params An array of two parameters:
263
+ * 1. a block hash
264
+ * 2. boolean to determine returning full transaction objects or just transaction hashes
77
265
*/
78
266
async getBlockByHash ( params : [ string , boolean ] ) {
79
267
const [ blockHash , includeTransactions ] = params
@@ -87,9 +275,8 @@ export class Eth {
87
275
}
88
276
89
277
/**
90
- * Returns the transaction count for a block given by the block hash
91
- * @param {Array<string> } [params] An array of one parameter: A block hash
92
- * @return {Promise }
278
+ * Returns the transaction count for a block given by the block hash.
279
+ * @param params An array of one parameter: A block hash
93
280
*/
94
281
async getBlockTransactionCountByHash ( params : [ string ] ) {
95
282
const [ blockHash ] = params
@@ -98,6 +285,92 @@ export class Eth {
98
285
return `0x${ json . transactions ! . length . toString ( 16 ) } `
99
286
}
100
287
288
+ /**
289
+ * Returns code of the account at the given address.
290
+ * Currently only "latest" block number is supported.
291
+ * @param params An array of two parameters:
292
+ * 1. address of the account
293
+ * 2. integer block number, or the string "latest", "earliest" or "pending"
294
+ */
295
+ async getCode ( params : [ string , string ] ) {
296
+ const [ addressHex , blockOpt ] = params
297
+
298
+ const latestBlockNumber = await this . blockNumber ( )
299
+ if ( blockOpt !== 'latest' && blockOpt !== latestBlockNumber ) {
300
+ // todo: this can be resolved with some kind of functionality of stateAt(blockNumber)
301
+ return {
302
+ code : INVALID_PARAMS ,
303
+ message : `Currently only block option "latest" supported` ,
304
+ }
305
+ }
306
+
307
+ if ( ! this . _vm ) {
308
+ throw new Error ( 'missing vm' )
309
+ }
310
+ const address = Address . fromString ( addressHex )
311
+ const code = await ( this . _vm . stateManager as any ) . getContractCode ( address )
312
+ return bufferToHex ( code )
313
+ }
314
+
315
+ /**
316
+ * Returns the value from a storage position at a given address.
317
+ * Currently only "latest" block number is supported.
318
+ * @param {Array<string> } [params] An array of three parameters:
319
+ * 1. address of the storage
320
+ * 2. integer of the position in the storage
321
+ * 3. integer block number, or the string "latest", "earliest" or "pending"
322
+ */
323
+ async getStorageAt ( params : [ string , string , string ] ) {
324
+ const [ addressHex , positionHex , blockOpt ] = params
325
+
326
+ const latestBlockNumber = await this . blockNumber ( )
327
+ if ( blockOpt !== 'latest' && blockOpt !== latestBlockNumber ) {
328
+ // todo: this can be resolved with some kind of functionality of stateAt(blockNumber)
329
+ return {
330
+ code : INVALID_PARAMS ,
331
+ message : `Currently only block option "latest" supported` ,
332
+ }
333
+ }
334
+
335
+ if ( ! this . _vm ) {
336
+ throw new Error ( 'missing vm' )
337
+ }
338
+
339
+ const address = Address . fromString ( addressHex )
340
+ const storageTrie = await ( this . _vm . stateManager as any ) . _getStorageTrie ( address )
341
+ const position = setLengthLeft ( toBuffer ( positionHex ) , 32 )
342
+ const storage = await storageTrie . get ( position )
343
+ return storage ? bufferToHex ( setLengthLeft ( decode ( storage ) , 32 ) ) : '0x'
344
+ }
345
+
346
+ /**
347
+ * Returns the number of transactions sent from an address.
348
+ * Currently only "latest" block number is supported.
349
+ * @param params An array of two parameters:
350
+ * 1. address of the account
351
+ * 2. integer block number, or the string "latest", "earliest" or "pending"
352
+ */
353
+ async getTransactionCount ( params : [ string , string ] ) {
354
+ const [ addressHex , blockOpt ] = params
355
+
356
+ const latestBlockNumber = await this . blockNumber ( )
357
+ if ( blockOpt !== 'latest' && blockOpt !== latestBlockNumber ) {
358
+ // todo: this can be resolved with some kind of functionality of stateAt(blockNumber)
359
+ return {
360
+ code : INVALID_PARAMS ,
361
+ message : `Currently only block option "latest" supported` ,
362
+ }
363
+ }
364
+
365
+ if ( ! this . _vm ) {
366
+ throw new Error ( 'missing vm' )
367
+ }
368
+
369
+ const address = Address . fromString ( addressHex )
370
+ const account : Account = await ( this . _vm . stateManager as any ) . getAccount ( address )
371
+ return `0x${ account . nonce . toString ( 16 ) } `
372
+ }
373
+
101
374
/**
102
375
* Returns the current ethereum protocol version as a hex-encoded string
103
376
* @param {Array<*> } [params] An empty array
0 commit comments