Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client: Add more unit tests for setHead function #3814

Merged
merged 5 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/client/src/rpc/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BIGINT_0, bigIntToHex, bytesToHex, intToHex } from '@ethereumjs/util'
import { INTERNAL_ERROR, INVALID_BLOCK, INVALID_PARAMS } from './error-code.js'

import type { Chain } from '../blockchain/index.js'
import type { RPCMethod } from './types.js'
import type { Block } from '@ethereumjs/block'
import type { JSONRPCTx, TypedTransaction } from '@ethereumjs/tx'

Expand All @@ -13,7 +14,7 @@ type RPCError = {
data?: string
}

export function callWithStackTrace(handler: Function, debug: boolean) {
export function callWithStackTrace(handler: Function, debug: boolean): RPCMethod {
return async (...args: any) => {
try {
const res = await handler(...args)
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/rpc/modules/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class Admin {
this._client = client
this._rpcDebug = rpcDebug

this.nodeInfo = middleware(callWithStackTrace(this.nodeInfo.bind(this), this._rpcDebug), 0, [])
this.peers = middleware(callWithStackTrace(this.peers.bind(this), this._rpcDebug), 0, [])
this.nodeInfo = callWithStackTrace(this.nodeInfo.bind(this), this._rpcDebug)
this.peers = callWithStackTrace(this.peers.bind(this), this._rpcDebug)
this.addPeer = middleware(callWithStackTrace(this.addPeer.bind(this), this._rpcDebug), 1, [
[
validators.object({
Expand Down
40 changes: 12 additions & 28 deletions packages/client/src/rpc/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,17 +315,14 @@ export class Eth {
const ethProtocol = this.service.protocols.find((p) => p.name === 'eth') as EthProtocol
this.ethVersion = Math.max(...ethProtocol.versions)

this.blockNumber = middleware(
callWithStackTrace(this.blockNumber.bind(this), this._rpcDebug),
0,
)
this.blockNumber = callWithStackTrace(this.blockNumber.bind(this), this._rpcDebug)

this.call = middleware(callWithStackTrace(this.call.bind(this), this._rpcDebug), 2, [
[validators.transaction()],
[validators.blockOption],
])

this.chainId = middleware(callWithStackTrace(this.chainId.bind(this), this._rpcDebug), 0, [])
this.chainId = callWithStackTrace(this.chainId.bind(this), this._rpcDebug)

this.estimateGas = middleware(
callWithStackTrace(this.estimateGas.bind(this), this._rpcDebug),
Expand All @@ -339,7 +336,7 @@ export class Eth {
[[validators.address], [validators.blockOption]],
)

this.coinbase = middleware(callWithStackTrace(this.coinbase.bind(this), this._rpcDebug), 0, [])
this.coinbase = callWithStackTrace(this.coinbase.bind(this), this._rpcDebug)

this.getBlockByNumber = middleware(
callWithStackTrace(this.getBlockByNumber.bind(this), this._rpcDebug),
Expand Down Expand Up @@ -443,13 +440,9 @@ export class Eth {
[[validators.hex]],
)

this.protocolVersion = middleware(
callWithStackTrace(this.protocolVersion.bind(this), this._rpcDebug),
0,
[],
)
this.protocolVersion = callWithStackTrace(this.protocolVersion.bind(this), this._rpcDebug)

this.syncing = middleware(callWithStackTrace(this.syncing.bind(this), this._rpcDebug), 0, [])
this.syncing = callWithStackTrace(this.syncing.bind(this), this._rpcDebug)

this.getProof = middleware(callWithStackTrace(this.getProof.bind(this), this._rpcDebug), 3, [
[validators.address],
Expand All @@ -463,7 +456,7 @@ export class Eth {
[[validators.blockOption]],
)

this.gasPrice = middleware(callWithStackTrace(this.gasPrice.bind(this), this._rpcDebug), 0, [])
this.gasPrice = callWithStackTrace(this.gasPrice.bind(this), this._rpcDebug)

this.feeHistory = middleware(
callWithStackTrace(this.feeHistory.bind(this), this._rpcDebug),
Expand All @@ -475,18 +468,13 @@ export class Eth {
],
)

this.blobBaseFee = middleware(
callWithStackTrace(this.blobBaseFee.bind(this), this._rpcDebug),
0,
[],
)
this.blobBaseFee = callWithStackTrace(this.blobBaseFee.bind(this), this._rpcDebug)
}

/**
* Returns number of the most recent block.
* @param params An empty array
*/
async blockNumber(_params = []) {
async blockNumber() {
return bigIntToHex(this._chain.headers.latest?.number ?? BIGINT_0)
}

Expand Down Expand Up @@ -540,10 +528,9 @@ export class Eth {

/**
* Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by EIP-155.
* @param _params An empty array
* @returns The chain ID.
*/
async chainId(_params = []) {
async chainId() {
const chainId = this._chain.config.chainCommon.chainId()
return bigIntToHex(chainId)
}
Expand Down Expand Up @@ -660,10 +647,9 @@ export class Eth {

/**
* Returns the currently configured coinbase address.
* @param _params An empty array
* @returns The chain ID.
*/
async coinbase(_params = []) {
async coinbase() {
const cb = this.client.config.minerCoinbase
if (cb === undefined) {
throw {
Expand Down Expand Up @@ -905,9 +891,8 @@ export class Eth {

/**
* Returns the current ethereum protocol version as a hex-encoded string
* @param params An empty array
*/
protocolVersion(_params = []) {
protocolVersion() {
return intToHex(this.ethVersion)
}

Expand Down Expand Up @@ -1272,13 +1257,12 @@ export class Eth {

/**
* Returns an object with data about the sync status or false.
* @param params An empty array
* @returns An object with sync status data or false (when not syncing)
* * startingBlock - The block at which the import started (will only be reset after the sync reached his head)
* * currentBlock - The current block, same as eth_blockNumber
* * highestBlock - The estimated highest block
*/
async syncing(_params = []) {
async syncing() {
if (this.client.config.synchronized) {
return false
}
Expand Down
24 changes: 6 additions & 18 deletions packages/client/src/rpc/modules/net.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { addHexPrefix } from '@ethereumjs/util'

import { callWithStackTrace } from '../helpers.js'
import { middleware } from '../validation.js'

import type { Chain } from '../../blockchain/index.js'
import type { EthereumClient } from '../../index.js'
Expand Down Expand Up @@ -29,40 +28,29 @@ export class Net {
this._peerPool = service.pool
this._rpcDebug = rpcDebug

this.version = middleware(callWithStackTrace(this.version.bind(this), this._rpcDebug), 0, [])
this.listening = middleware(
callWithStackTrace(this.listening.bind(this), this._rpcDebug),
0,
[],
)
this.peerCount = middleware(
callWithStackTrace(this.peerCount.bind(this), this._rpcDebug),
0,
[],
)
this.version = callWithStackTrace(this.version.bind(this), this._rpcDebug)
this.listening = callWithStackTrace(this.listening.bind(this), this._rpcDebug)
this.peerCount = callWithStackTrace(this.peerCount.bind(this), this._rpcDebug)
}

/**
* Returns the current network id
* @param params An empty array
*/
version(_params = []) {
version() {
return this._chain.config.chainCommon.chainId().toString()
}

/**
* Returns true if client is actively listening for network connections
* @param params An empty array
*/
listening(_params = []) {
listening() {
return this._client.opened
}

/**
* Returns number of peers currently connected to the client
* @param params An empty array
*/
peerCount(_params = []) {
peerCount() {
return addHexPrefix(this._peerPool.peers.length.toString(16))
}
}
6 changes: 2 additions & 4 deletions packages/client/src/rpc/modules/txpool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { callWithStackTrace, toJSONRPCTx } from '../helpers.js'
import { middleware } from '../validation.js'

import type { EthereumClient } from '../../index.js'
import type { FullEthereumService } from '../../service/index.js'
Expand All @@ -25,14 +24,13 @@ export class TxPool {
this._vm = service.execution.vm
this._rpcDebug = rpcDebug

this.content = middleware(callWithStackTrace(this.content.bind(this), this._rpcDebug), 0, [])
this.content = callWithStackTrace(this.content.bind(this), this._rpcDebug)
}

/**
* Returns the contents of the transaction pool
* @param params An empty array
*/
content(_params = []) {
content() {
const pending = new Map()
for (const pool of this._txpool.pool) {
const pendingForAcct = new Map<bigint, any>()
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/rpc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ export function toRPCTx(t: TxResult): RPCTx {
t.to !== null && (rpcTx.to = t.to)
return rpcTx
}

export type RPCMethod = (...params: any) => any
5 changes: 4 additions & 1 deletion packages/client/src/rpc/validation.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { INVALID_PARAMS } from './error-code.js'

import type { RPCMethod } from './types.js'

/**
* middleware for parameters validation
* @memberof module:rpc
* @param method function to add middleware
* @param requiredParamsCount required parameters count
* @param validators array of validators
* @param names Optional parameter names for error messages, length must be equal to requiredParamsCount
*/
export function middleware(
method: any,
requiredParamsCount: number,
validators: any[] = [],
names: string[] = [],
): any {
): RPCMethod {
return function (params: any[] = []) {
return new Promise((resolve, reject) => {
if (params.length < requiredParamsCount) {
Expand Down
56 changes: 48 additions & 8 deletions packages/client/test/rpc/debug/setHead.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,71 @@ describe(method, async () => {
const exec = await testSetup(blockchain)
await exec.run()
const newHead = await (exec.vm.blockchain as Blockchain).getIteratorHead!()
assert.equal(newHead.header.number, BigInt(5), 'should run all blocks')
assert.equal(newHead.header.number, BigInt(mainnetData.length), 'should run all blocks')

const a = await createClient({ blockchain })
await a.service.skeleton?.open()
;(a.service.execution as any) = exec
const client = await createClient({ blockchain })
await client.service.skeleton?.open()
;(client.service.execution as any) = exec

const manager = createManager(a)
const manager = createManager(client)
const rpc = getRPCClient(startRPC(manager.getMethods()))
assert.equal(
await a.service.skeleton?.headHash(),
await client.service.skeleton?.headHash(),
undefined,
'should return undefined when head is not set',
)
for (let i = 0; i < blocks.length; i++) {
await rpc.request(method, [`0x${i}`])
assert.deepEqual(
await a.service.skeleton?.headHash()!,
await client.service.skeleton?.headHash()!,
blocks[i].header.hash(),
`skeleton chain should return hash of block number ${i} set as head`,
)
assert.deepEqual(
a.service.execution.chainStatus?.hash!,
client.service.execution.chainStatus?.hash!,
blocks[i].header.hash(),
`vm execution should set hash to new head`,
)
}
}, 30000)

it('should return error for pending block', async () => {
const blockchain = await createBlockchainFromBlocksData(mainnetData, {
validateBlocks: true,
validateConsensus: false,
})

const client = await createClient({ blockchain })

const manager = createManager(client)
const rpc = getRPCClient(startRPC(manager.getMethods()))
const result = await rpc.request(method, ['pending'])
assert.equal(result.error.code, -32602)
assert.equal(result.error.message, '"pending" is not supported')
})

it('should handle internal errors', async () => {
const blockchain = await createBlockchainFromBlocksData(mainnetData, {
validateBlocks: true,
validateConsensus: false,
})
const exec = await testSetup(blockchain)
await exec.run()
const newHead = await (exec.vm.blockchain as Blockchain).getIteratorHead!()
assert.equal(newHead.header.number, BigInt(mainnetData.length), 'should run all blocks')

const client = await createClient({ blockchain })
;(client.service.skeleton as any) = {
open: async () => {
throw new Error('open failed')
},
}

const manager = createManager(client)
const rpc = getRPCClient(startRPC(manager.getMethods()))

const result = await rpc.request(method, ['0x1'])
assert.equal(result.error.code, -32603)
assert.equal(result.error.message, 'Internal error')
})
})
Loading