Skip to content

Commit

Permalink
♻️ Chore: move newFilter to actions package
Browse files Browse the repository at this point in the history
  • Loading branch information
William Cory authored and William Cory committed Aug 1, 2024
1 parent 7529b8e commit d140058
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 102 deletions.
105 changes: 105 additions & 0 deletions packages/actions/src/eth/ethNewFilterHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { createAddress } from '@tevm/address'
import { bytesToHex, hexToBytes } from '@tevm/utils'
import { generateRandomId } from './utils/generateRandomId.js'
import { parseBlockTag } from './utils/parseBlockTag.js'
import { InvalidBlockError, UnknownBlockError } from '@tevm/errors'

/**
* @typedef {UnknownBlockError | InvalidBlockError} EthNewFilterError
*/

/**
* @param {import('@tevm/base-client').BaseClient} client
* @returns {import('./EthHandler.js').EthNewFilterHandler} ethNewFilterHandler
*/
export const ethNewFilterHandler = (client) => {
return async (params) => {
const { topics, address, toBlock = 'latest', fromBlock = 'latest' } = params
const vm = await client.getVm()
/**
* @param {typeof toBlock} tag
*/
const getBlock = async (tag) => {
const parsedTag = parseBlockTag(tag)
if (
parsedTag === 'safe' ||
parsedTag === 'latest' ||
parsedTag === 'finalized' ||
parsedTag === 'earliest' ||
parsedTag === 'pending' ||
parsedTag === /** @type any*/ ('forked')
) {
return vm.blockchain.blocksByTag.get(parsedTag)
}
if (typeof parsedTag === 'string') {
return vm.blockchain.getBlock(hexToBytes(parsedTag))
}
if (typeof tag === 'bigint') {
return vm.blockchain.getBlock(tag)
}
throw new InvalidBlockError(`Invalid block tag ${tag}`)
}
const _toBlock = await getBlock(toBlock)
if (!_toBlock) {
throw new UnknownBlockError(`Unknown block tag ${toBlock}`)
}
const _fromBlock = await getBlock(fromBlock)
if (!_fromBlock) {
throw new UnknownBlockError(`Unknown block tag ${fromBlock}`)
}

const id = generateRandomId()
/**
* @param {import('@tevm/base-client').Filter['logs'][number]} log
*/
const listener = (log) => {
const filter = client.getFilters().get(id)
if (!filter) {
return
}
filter.logs.push(log)
}
client.on('newLog', listener)
// populate with past blocks
const receiptsManager = await client.getReceiptsManager()
const pastLogs = await receiptsManager.getLogs(
_fromBlock,
_toBlock,
address !== undefined ? [createAddress(address).bytes] : [],
topics?.map((topic) => hexToBytes(topic)),
)
client.setFilter({
id,
type: 'Log',
created: Date.now(),
logs: pastLogs.map((log) => {
const [address, topics, data] = log.log
return {
topics: /** @type {[import('@tevm/utils').Hex, ...Array<import('@tevm/utils').Hex>]}*/ (
topics.map((topic) => bytesToHex(topic))
),
address: bytesToHex(address),
data: bytesToHex(data),
blockNumber: log.block.header.number,
transactionHash: bytesToHex(log.tx.hash()),
removed: false,
logIndex: log.logIndex,
blockHash: bytesToHex(log.block.hash()),
transactionIndex: log.txIndex,
}
}),
tx: [],
blocks: [],
logsCriteria: {
topics,
address,
toBlock: toBlock,
fromBlock: fromBlock,
},
installed: {},
err: undefined,
registeredListeners: [listener],
})
return id
}
}
1 change: 1 addition & 0 deletions packages/actions/src/eth/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './chainIdHandler.js'
export * from './getCodeHandler.js'
export * from './gasPriceHandler.js'
export * from './ethNewFilterHandler.js'
export * from './blockNumberHandler.js'
export * from './getBalanceHandler.js'
export * from './getStorageAtHandler.js'
Expand Down
8 changes: 8 additions & 0 deletions packages/actions/src/eth/utils/generateRandomId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @returns {import("@tevm/utils").Hex}
*/
export const generateRandomId = () => {
return `0x${Array.from(crypto.getRandomValues(new Uint8Array(16)))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')}`
}
15 changes: 15 additions & 0 deletions packages/actions/src/eth/utils/generateRandomId.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest'
import { generateRandomId } from './generateRandomId.js'

describe('generateRandomId', () => {
it('should generate a valid hex string of length 34', () => {
const id = generateRandomId()
expect(id).toMatch(/^0x[a-f0-9]{32}$/)
})

it('should generate different ids on multiple calls', () => {
const id1 = generateRandomId()
const id2 = generateRandomId()
expect(id1).not.toBe(id2)
})
})
14 changes: 14 additions & 0 deletions packages/actions/src/eth/utils/parseBlockTag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { hexToBigInt } from '@tevm/utils'

/**
* @param {import('@tevm/utils').Hex | import('@tevm/utils').BlockTag | bigint} blockTag
* @returns {bigint | import('@tevm/utils').Hex | import('@tevm/utils').BlockTag}
*/
export const parseBlockTag = (blockTag) => {
const blockHashLength = 64 + '0x'.length
const isBlockNumber = typeof blockTag === 'string' && blockTag.startsWith('0x') && blockTag.length !== blockHashLength
if (isBlockNumber) {
return hexToBigInt(/** @type {import('@tevm/utils').Hex}*/ (blockTag))
}
return blockTag
}
43 changes: 43 additions & 0 deletions packages/actions/src/eth/utils/parseBlockTag.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { hexToBigInt } from '@tevm/utils'
import { describe, expect, it } from 'vitest'
import { parseBlockTag } from './parseBlockTag.js'

describe('parseBlockTag', () => {
it('should parse hex block numbers to bigint', () => {
const blockTag = '0x10'
const result = parseBlockTag(blockTag)
expect(result).toBe(hexToBigInt(blockTag))
})

it('should return block hash as is', () => {
const blockHash = `0x${'a'.repeat(64)}` as const
const result = parseBlockTag(blockHash)
expect(result).toBe(blockHash)
})

it('should return special block tags as is', () => {
const tags = ['latest', 'earliest', 'pending'] as const
tags.forEach((tag) => {
const result = parseBlockTag(tag)
expect(result).toBe(tag)
})
})

it('should return block number as bigint for valid hex strings', () => {
const blockTag = '0x1a'
const result = parseBlockTag(blockTag)
expect(result).toBe(26n)
})

it('should handle block tag as a number string correctly', () => {
const blockTag = '0x10'
const result = parseBlockTag(blockTag)
expect(result).toBe(16n)
})

it('should return blockTag unchanged if it is a non-hex string', () => {
const blockTag = 'pending'
const result = parseBlockTag(blockTag)
expect(result).toBe(blockTag)
})
})
115 changes: 13 additions & 102 deletions packages/procedures/src/eth/ethNewFilterProcedure.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { createAddress } from '@tevm/address'
import { bytesToHex, hexToBytes } from '@tevm/utils'
import { generateRandomId } from '../utils/generateRandomId.js'
import { parseBlockTag } from '../utils/parseBlockTag.js'
import { ethNewFilterHandler } from '@tevm/actions'

/**
* Request handler for eth_newFilter JSON-RPC requests.
Expand All @@ -11,111 +8,25 @@ import { parseBlockTag } from '../utils/parseBlockTag.js'
export const ethNewFilterJsonRpcProcedure = (client) => {
return async (request) => {
const newFilterRequest = /** @type {import('./EthJsonRpcRequest.js').EthNewFilterJsonRpcRequest}*/ (request)

const { topics, address, toBlock = 'latest', fromBlock = 'latest' } = newFilterRequest.params[0]
const id = generateRandomId()
const vm = await client.getVm()
/**
* @param {typeof toBlock} tag
*/
const getBlock = async (tag) => {
const parsedTag = parseBlockTag(tag)
if (
parsedTag === 'safe' ||
parsedTag === 'latest' ||
parsedTag === 'finalized' ||
parsedTag === 'earliest' ||
parsedTag === 'pending' ||
parsedTag === /** @type any*/ ('forked')
) {
return vm.blockchain.blocksByTag.get(parsedTag)
}
if (typeof parsedTag === 'string') {
return vm.blockchain.getBlock(hexToBytes(parsedTag))
}
return vm.blockchain.getBlock(parsedTag)
}
const _toBlock = await getBlock(toBlock)
if (!_toBlock) {
try {
return {
...(request.id ? { id: request.id } : {}),
method: request.method,
jsonrpc: request.jsonrpc,
error: {
code: -32602,
message: `Invalid block tag ${toBlock}`,
},
method: request.method,
result: await ethNewFilterHandler(client)(newFilterRequest.params[0]),
...(request.id !== undefined ? { id: request.id } : {}),
}
}
const _fromBlock = await getBlock(fromBlock)
if (!_fromBlock) {
} catch (e) {
client.logger.error(e)
const { code, message } = /** @type {import('@tevm/actions').EthNewFilterError}*/ (e)
return {
...(request.id ? { id: request.id } : {}),
method: request.method,
jsonrpc: request.jsonrpc,
error: {
code: -32602,
message: `Invalid block tag ${fromBlock}`,
code,
message,
},
method: request.method,
jsonrpc: request.jsonrpc,
...(request.id !== undefined ? { id: request.id } : {}),
}
}

/**
* @param {import('@tevm/base-client').Filter['logs'][number]} log
*/
const listener = (log) => {
const filter = client.getFilters().get(id)
if (!filter) {
return
}
filter.logs.push(log)
}
client.on('newLog', listener)
// populate with past blocks
const receiptsManager = await client.getReceiptsManager()
const pastLogs = await receiptsManager.getLogs(
_fromBlock,
_toBlock,
address !== undefined ? [createAddress(address).bytes] : [],
topics?.map((topic) => hexToBytes(topic)),
)
client.setFilter({
id,
type: 'Log',
created: Date.now(),
logs: pastLogs.map((log) => {
const [address, topics, data] = log.log
return {
topics: /** @type {[import('@tevm/utils').Hex, ...Array<import('@tevm/utils').Hex>]}*/ (
topics.map((topic) => bytesToHex(topic))
),
address: bytesToHex(address),
data: bytesToHex(data),
blockNumber: log.block.header.number,
transactionHash: bytesToHex(log.tx.hash()),
removed: false,
logIndex: log.logIndex,
blockHash: bytesToHex(log.block.hash()),
transactionIndex: log.txIndex,
}
}),
tx: [],
blocks: [],
logsCriteria: {
topics,
address,
toBlock: toBlock,
fromBlock: fromBlock,
},
installed: {},
err: undefined,
registeredListeners: [listener],
})
return {
...(request.id ? { id: request.id } : {}),
method: request.method,
jsonrpc: request.jsonrpc,
result: id,
}
}
}

0 comments on commit d140058

Please sign in to comment.