Skip to content

Commit a7414cd

Browse files
ryanioholgerd77
authored andcommitted
add rpc methods, add new rpc input validators and tests, refactor rpc test helper code
1 parent 87f44a2 commit a7414cd

18 files changed

+1159
-59
lines changed

packages/client/lib/rpc/modules/eth.ts

Lines changed: 293 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,66 @@
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'
113
import { Chain } from '../../blockchain'
214
import { middleware, validators } from '../validation'
3-
import { BN, bufferToHex, toBuffer, stripHexPrefix } from 'ethereumjs-util'
415
import { EthereumClient } from '../..'
16+
import { INVALID_PARAMS } from '../error-code'
17+
import { RpcCallTx } from '../types'
18+
import type VM from '@ethereumjs/vm'
519

620
/**
721
* eth_* RPC module
822
* @memberof module:rpc/modules
923
*/
1024
export class Eth {
1125
private _chain: Chain
26+
private _vm: VM | undefined
1227
public ethVersion: number
1328

1429
/**
1530
* Create eth_* RPC module
16-
* @param {Node} Node to which the module binds
31+
* @param client Client to which the module binds
1732
*/
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)
2346

2447
this.blockNumber = middleware(this.blockNumber.bind(this), 0)
2548

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+
2664
this.getBlockByNumber = middleware(this.getBlockByNumber.bind(this), 2, [
2765
[validators.hex],
2866
[validators.bool],
@@ -39,24 +77,174 @@ export class Eth {
3977
[[validators.hex, validators.blockHash]]
4078
)
4179

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+
4296
this.protocolVersion = middleware(this.protocolVersion.bind(this), 0, [])
4397
}
4498

4599
/**
46100
* Returns number of the most recent block.
47-
* @param {Array<*>} [params] An empty array
48-
* @return {Promise}
101+
* @param params An empty array
49102
*/
50103
async blockNumber(_params = []) {
51104
const latestHeader = await this._chain.getLatestHeader()
52105
return `0x${latestHeader.number.toString(16)}`
53106
}
54107

55108
/**
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
60248
*/
61249
async getBlockByNumber(params: [string, boolean]) {
62250
const [blockNumber, includeTransactions] = params
@@ -70,10 +258,10 @@ export class Eth {
70258
}
71259

72260
/**
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
77265
*/
78266
async getBlockByHash(params: [string, boolean]) {
79267
const [blockHash, includeTransactions] = params
@@ -87,9 +275,8 @@ export class Eth {
87275
}
88276

89277
/**
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
93280
*/
94281
async getBlockTransactionCountByHash(params: [string]) {
95282
const [blockHash] = params
@@ -98,6 +285,92 @@ export class Eth {
98285
return `0x${json.transactions!.length.toString(16)}`
99286
}
100287

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+
101374
/**
102375
* Returns the current ethereum protocol version as a hex-encoded string
103376
* @param {Array<*>} [params] An empty array

packages/client/lib/rpc/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface RpcCallTx {
2+
from?: string
3+
to: string
4+
gas?: string
5+
gasPrice?: string
6+
value?: string
7+
data?: string
8+
}

0 commit comments

Comments
 (0)