Skip to content

Commit

Permalink
format response to match jsonrpc spec and update test files
Browse files Browse the repository at this point in the history
  • Loading branch information
cjustinobi authored and cjustinobi committed Dec 30, 2024
1 parent 3e0673f commit 56d2f9a
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 16 deletions.
109 changes: 106 additions & 3 deletions packages/cli/scripts/testProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
import { UltralightProvider } from 'portalnetwork'
import { UltralightProvider } from '../../portalnetwork/src/client/provider'

const testBlockHash = '0x95b0950557cbc3e6647766adb719f80f7c7d192f4429b6026cdbd2cbe6a64294'
const testContract = '0x6b175474e89094c44da98b954eedeac495271d0f'
const testStorage = '0x0000000000000000000000000000000000000000000000000000000000000000'
const historicalBlock = 31591

async function findHistoricalAccount(provider: UltralightProvider, blockNumber: number): Promise<string | null> {
try {
const block: any = await provider.request({
method: 'eth_getBlockByNumber',
params: [blockNumber, true]
})

if (block && block.result && block.result.transactions && block.result.transactions.length > 0) {

const contractCreation = block.result.transactions.find((tx: any) => !tx.to)
if (contractCreation) {
console.log('Found contract creation transaction')
return contractCreation.from
}

console.log('Using first transaction sender')
return block.result.transactions[0].from
}
return null
} catch (error) {
console.error('Error finding historical account:', error)
return null
}
}

async function main() {
const provider = await UltralightProvider.create({
Expand All @@ -27,12 +55,87 @@ async function main() {
console.log('Waiting for network to start...')
await new Promise((resolve) => setTimeout(resolve, 1000))
}
const block = await provider.request({

console.log('Testing eth_getBlockByHash...')
const block: any = await provider.request({
method: 'eth_getBlockByHash',
params: [testBlockHash, false],
})

console.log('Block retrieved:', block)
console.log('Block by hash retrieved:', block)

console.log('Testing eth_getBlockByNumber...')
const blockByNumber = await provider.request({
method: 'eth_getBlockByNumber',
params: [100, false]
})

console.log('Block by number retrieved:', blockByNumber)

console.log(`Looking for accounts in block ${historicalBlock}...`)

const testAddress = await findHistoricalAccount(provider, historicalBlock)
if (!testAddress) {
console.error('Could not find a historical account to test with')
}

console.log(`Found historical address: ${testAddress}`)

console.log('Testing eth_getTransactionCount...')
const transactionCount = await provider.request({
method: 'eth_getTransactionCount',
params: [testAddress, '31591'],
})

console.log('Transaction count:', transactionCount)


console.log('Testing eth_getCode...')
const code = await provider.request({
method: 'eth_getCode',
params: [testContract, '100']
})
console.log('Contract code retrieved:', code)


console.log('Testing eth_getBalance...')
const balance = await provider.request({
method: 'eth_getBalance',
params: [testAddress, '31591']
})
console.log('Account balance:', balance)


console.log('Testing eth_getStorageAt...')
const storage = await provider.request({
method: 'eth_getStorageAt',
params: [testContract, testStorage, '31591']
})
console.log('Storage value:', storage)


console.log('Testing eth_call...')
const callData = {
to: testAddress,
data: '0x06fdde03',
from: testContract,
}
const callResult = await provider.request({
method: 'eth_call',
params: [callData, 31591]
})
console.log('Contract call result:', callResult)


try {
await provider.request({
method: 'eth_unsupportedMethod',
params: []
})
} catch (error) {
console.log('Expected error for unsupported method:', error.message)
}

process.exit(0)
}

Expand Down
25 changes: 21 additions & 4 deletions packages/portalnetwork/src/client/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { PortalNetwork } from './client.js'
import type { PortalNetworkOpts } from './types'
import { hexToBytes } from '@ethereumjs/util'

import { formatBlockResponse } from '../util/helpers'

const ERROR_CODES = {
UNSUPPORTED_METHOD: 4200,
INVALID_PARAMS: -32602,
Expand Down Expand Up @@ -57,6 +59,7 @@ export class UltralightProvider {
}

case 'eth_getBlockByNumber': {
console.log('inside eth_getBlockByNumber')
if (params.length !== 2)
throw this.createError(
ERROR_CODES.INVALID_PARAMS,
Expand Down Expand Up @@ -143,18 +146,32 @@ export class UltralightProvider {

private async getBlockByHash(blockHash: Uint8Array, fullTx: boolean) {
const response = await this.portal.ETH.getBlockByHash(blockHash, fullTx)
return response
if (!response) {
throw this.createError(ERROR_CODES.INTERNAL_ERROR, 'Block not found')
}

return formatBlockResponse(response, fullTx)
}

private async getBlockByNumber(blockNumber: string | number | bigint, includeTx: boolean) {

let block

if (typeof blockNumber === 'string') {
if (blockNumber === 'latest' || blockNumber === 'finalized') {
return this.portal.ETH.getBlockByNumber(blockNumber, includeTx)
block = await this.portal.ETH.getBlockByNumber(blockNumber, includeTx)
} else {
block = await this.portal.ETH.getBlockByNumber(BigInt(blockNumber), includeTx)
}
return this.portal.ETH.getBlockByNumber(BigInt(blockNumber), includeTx)
} else {
block = await this.portal.ETH.getBlockByNumber(blockNumber, includeTx)
}

if (!block) {
throw this.createError(ERROR_CODES.INTERNAL_ERROR, 'Block not found')
}

return this.portal.ETH.getBlockByNumber(blockNumber, includeTx)
return formatBlockResponse(block, includeTx)
}

private async getTransactionCount(address: Uint8Array, block: string) {
Expand Down
2 changes: 1 addition & 1 deletion packages/portalnetwork/src/util/discv5.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { distance, IDiscv5CreateOptions, log2Distance } from '@chainsafe/discv5'
export { distance, log2Distance } from '@chainsafe/discv5'
80 changes: 79 additions & 1 deletion packages/portalnetwork/src/util/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Block, BlockHeader } from '@ethereumjs/block'
import { TransactionFactory } from '@ethereumjs/tx'
import { TypeOutput, bytesToHex, setLengthLeft, toBytes, toType } from '@ethereumjs/util'
import { TypeOutput, bigIntToHex, bytesToHex, intToHex, setLengthLeft, toBytes, toType } from '@ethereumjs/util'
import { VM } from '@ethereumjs/vm'
import debug from 'debug'
import { ethers } from 'ethers'
Expand Down Expand Up @@ -291,3 +291,81 @@ export function blockFromRpc(
{ ...options, setHardfork: true },
)
}

export function formatBlockResponse(block: Block, includeTransactions: boolean) {
const json = JSON.stringify(block, null, 2)
const parsedBlock = JSON.parse(json)
const header = parsedBlock.header

const withdrawalsAttr =
header.withdrawalsRoot !== undefined
? {
withdrawalsRoot: header.withdrawalsRoot!,
withdrawals: parsedBlock.withdrawals,
}
: {}

const transactions = block.transactions.map((tx, txIndex) =>
includeTransactions ? toJSONRPCTx(tx, block, txIndex) : bytesToHex(tx.hash()),
)

return {
jsonrpc: '2.0',
id: 1,
result: {
number: header.number,
hash: bytesToHex(block.hash()),
parentHash: header.parentHash,
mixHash: header.mixHash,
nonce: header.nonce!,
sha3Uncles: header.uncleHash!,
logsBloom: header.logsBloom!,
transactionsRoot: header.transactionsTrie!,
stateRoot: header.stateRoot!,
receiptsRoot: header.receiptTrie!,
miner: header.coinbase!,
difficulty: header.difficulty!,
extraData: header.extraData!,
size: intToHex(block.serialize().length),
gasLimit: header.gasLimit!,
gasUsed: header.gasUsed!,
timestamp: header.timestamp!,
transactions,
uncles: block.uncleHeaders.map((uh) => bytesToHex(uh.hash())),
baseFeePerGas: header.baseFeePerGas,
...withdrawalsAttr,
blobGasUsed: header.blobGasUsed,
excessBlobGas: header.excessBlobGas,
parentBeaconBlockRoot: header.parentBeaconBlockRoot,
requestsRoot: header.requestsRoot,
requests: block.requests?.map((req) => bytesToHex(req.serialize())),
},
}
}

export function toJSONRPCTx (tx: TypedTransaction, block?: Block, txIndex?: number) {
const txJSON = tx.toJSON()
return {
blockHash: block ? bytesToHex(block.hash()) : null,
blockNumber: block ? bigIntToHex(block.header.number) : null,
from: tx.getSenderAddress().toString(),
gas: txJSON.gasLimit!,
gasPrice: txJSON.gasPrice ?? txJSON.maxFeePerGas!,
maxFeePerGas: txJSON.maxFeePerGas,
maxPriorityFeePerGas: txJSON.maxPriorityFeePerGas,
type: intToHex(tx.type),
accessList: txJSON.accessList,
chainId: txJSON.chainId,
hash: bytesToHex(tx.hash()),
input: txJSON.data!,
nonce: txJSON.nonce!,
to: tx.to?.toString() ?? null,
transactionIndex: txIndex !== undefined ? intToHex(txIndex) : null,
value: txJSON.value!,
v: txJSON.v!,
r: txJSON.r!,
s: txJSON.s!,
maxFeePerBlobGas: txJSON.maxFeePerBlobGas,
blobVersionedHashes: txJSON.blobVersionedHashes
}
}
86 changes: 79 additions & 7 deletions packages/portalnetwork/test/client/provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SignableENR } from '@chainsafe/enr'
import { Block, BlockHeader } from '@ethereumjs/block'
import { keys } from '@libp2p/crypto'
import { multiaddr } from '@multiformats/multiaddr'
import { assert, it } from 'vitest'
import { expect, it } from 'vitest'

import { UltralightProvider } from '../../src/client/provider.js'
import { TransportLayer } from '../../src/index.js'
Expand All @@ -14,7 +14,7 @@ it('Test provider functionality', async () => {
const enr = SignableENR.createFromPrivateKey(privateKey)
enr.setLocationMultiaddr(ma)
const provider = await UltralightProvider.create({
bindAddress: '0.0.0.0',
bindAddress: '0.0.0.0.0',
transport: TransportLayer.NODE,
config: {
bindAddrs: {
Expand All @@ -31,12 +31,84 @@ it('Test provider functionality', async () => {
provider.portal.ETH.getBlockByHash = async (_hash: Uint8Array) => {
return Block.fromBlockData({ header: BlockHeader.fromHeaderData({ number: 2n }) })
}

provider.portal.ETH.getBlockByNumber = async (blockNumber: number | bigint | "latest" | "finalized") => {
return Block.fromBlockData({
header: BlockHeader.fromHeaderData({
number: typeof blockNumber === 'string' ? 0n : blockNumber,

})
})
}

provider.portal.ETH.getTransactionCount = async (_address: Uint8Array) => {
return BigInt('0x5')
}

provider.portal.ETH.getCode = async (_address: Uint8Array) => {
return new Uint8Array(Buffer.from('60806040', 'hex'))
}

provider.portal.ETH.getBalance = async (_address: Uint8Array) => {
return 1000000000000000000n
}

provider.portal.ETH.getStorageAt = async (_address: Uint8Array, _position: Uint8Array) => {
return '0x' + Buffer.from(new Uint8Array(32)).toString('hex')
}

provider.portal.ETH.call = async (_txObject: any) => {
return new Uint8Array([0x00, 0x01])
}

const blockByHash = await provider.request({
method: 'eth_getBlockByHash',
params: ['0x123', false]
}) as { result: { number: string } }
expect(blockByHash.result.number).toBe('0x2')

const blockByNumber = await provider.request({
method: 'eth_getBlockByNumber',
params: [100, false]
}) as { result: { number: string } }
expect(blockByNumber.result.number).toBe('0x64')

const balance = await provider.request({
method: 'eth_getBalance',
params: ['0x1234567890123456789012345678901234567890', '0x0']
}) as { result: string }
expect(balance.result).toBe('0xde0b6b3a7640000')

const storage = await provider.request({
method: 'eth_getStorageAt',
params: [
'0x1234567890123456789012345678901234567890',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x64'
]
}) as { result: string }
expect(storage.result).toBe('0x' + '00'.repeat(32))

const call = await provider.request({
method: 'eth_call',
params: [{
to: '0x1234567890123456789012345678901234567890',
data: '0x70a08231000000000000000000000000'
}, '0x64']
}) as { result: string }
expect(call.result).toBe('0x0001')

await expect(provider.request({
method: 'eth_unsupportedMethod',
params: []
})).rejects.toThrow()

await expect(provider.request({
method: 'eth_getBlockByHash',
params: ['0x123']
})).rejects.toThrow()


await (provider as any).portal.stop()

assert.equal(
1n,
(await provider._detectNetwork()).chainId,
'parent class methods work as expected',
)
})

0 comments on commit 56d2f9a

Please sign in to comment.