Skip to content
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
93 changes: 65 additions & 28 deletions packages/cli/src/rpc/modules/portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { EntryStatus, distance } from '@chainsafe/discv5'
import { ENR } from '@chainsafe/enr'
import { bigIntToHex, bytesToHex, hexToBytes, short } from '@ethereumjs/util'
import {
ClientInfoAndCapabilities,
ContentLookup,
FoundContent,
NetworkId,
NodeLookup,
decodeExtensionPayloadToJson,
encodeExtensionPayloadFromJson,
shortId,
} from 'portalnetwork'

Expand All @@ -15,10 +16,10 @@ import { content_params } from '../schema/index.js'
import { callWithStackTrace, isValidId } from '../util.js'
import { middleware, validators } from '../validators.js'

import { RunStatusCode } from '@lodestar/light-client'
import type { Debugger } from 'debug'
import type { BeaconNetwork, HistoryNetwork, PortalNetwork, StateNetwork } from 'portalnetwork'
import type { GetEnrResult } from '../schema/types.js'
import { RunStatusCode } from '@lodestar/light-client'

const methods = [
// state
Expand Down Expand Up @@ -137,9 +138,21 @@ export class portal {
])

// portal_*Ping
this.historyPing = middleware(this.historyPing.bind(this), 1, [[validators.enr]])
this.statePing = middleware(this.statePing.bind(this), 1, [[validators.enr]])
this.beaconPing = middleware(this.beaconPing.bind(this), 1, [[validators.enr]])
this.historyPing = middleware(callWithStackTrace(this.historyPing.bind(this), true), 2, [
[validators.enr],
[validators.extension],
[validators.optional(validators.payload)],
])
this.statePing = middleware(this.statePing.bind(this), 2, [
[validators.enr],
[validators.extension],
[validators.optional(validators.payload)],
])
this.beaconPing = middleware(this.beaconPing.bind(this), 2, [
[validators.enr],
[validators.extension],
[validators.optional(validators.payload)],
])

// portal_*FindNodes
this.historyFindNodes = middleware(this.historyFindNodes.bind(this), 2, [
Expand Down Expand Up @@ -551,52 +564,76 @@ export class portal {
return true
}
// portal_*Ping
async historyPing(params: [string]) {
const [enr] = params
async historyPing(params: [string, number, object | undefined]) {
const [enr, ext, payload] = params
const encodedENR = ENR.decodeTxt(enr)
this.logger(`PING request received on HistoryNetwork for ${shortId(encodedENR.nodeId)}`)
const pong = await this._history.sendPing(encodedENR, 0)
const extension = ext ?? 0
this.logger(
`PING request received on HistoryNetwork for ${shortId(encodedENR.nodeId)} with extension ${extension}`,
)

let encodedPayload = undefined
if (payload !== undefined) {
encodedPayload = encodeExtensionPayloadFromJson(extension, payload)
}
const pong = await this._history.sendPing(encodedENR, extension, encodedPayload)
if (pong) {
this.logger(`PING/PONG successful with ${encodedENR.nodeId}`)
// const decoded = CustomPayloadExtensionsFormat.deserialize(pong.customPayload)
const { DataRadius } = ClientInfoAndCapabilities.deserialize(pong.customPayload)
return {
enrSeq: Number(pong.enrSeq),
dataRadius: bigIntToHex(DataRadius),
payloadType: pong.payloadType,
payload: decodeExtensionPayloadToJson(pong.payloadType, pong.customPayload),
}
} else {
this.logger(`PING/PONG with ${encodedENR.nodeId} was unsuccessful`)
return false
}
}
async statePing(params: [string]) {
const [enr] = params
async statePing(params: [string, number, object | undefined]) {
const [enr, ext, payload] = params
const encodedENR = ENR.decodeTxt(enr)
this.logger(`PING request received on StateNetwork for ${shortId(encodedENR.nodeId)}`)
const pong = await this._state.sendPing(encodedENR)
const extension = ext ?? 0
this.logger(
`PING request received on StateNetwork for ${shortId(encodedENR.nodeId)} with extension ${extension}`,
)
let encodedPayload = undefined
if (payload !== undefined) {
encodedPayload = encodeExtensionPayloadFromJson(extension, payload)
}
const pong = await this._state.sendPing(encodedENR, extension, encodedPayload)
if (pong) {
this.logger(`PING/PONG successful with ${encodedENR.nodeId}`)
const { DataRadius } = ClientInfoAndCapabilities.deserialize(pong.customPayload)

return {
enrSeq: Number(pong.enrSeq),
dataRadius: bigIntToHex(DataRadius),
payloadType: pong.payloadType,
payload: decodeExtensionPayloadToJson(pong.payloadType, pong.customPayload),
}
} else {
this.logger(`PING/PONG with ${encodedENR.nodeId} was unsuccessful`)
return false
}
}
async beaconPing(params: [string]) {
const [enr] = params
async beaconPing(params: [string, number, object | undefined]) {
const [enr, ext, payload] = params
const encodedENR = ENR.decodeTxt(enr)
this.logger(`PING request received on BeaconNetwork for ${shortId(encodedENR.nodeId)}`)
const pong = await this._beacon.sendPing(encodedENR)
const extension = ext ?? 0
this.logger(
`PING request received on BeaconNetwork for ${shortId(encodedENR.nodeId)} with extension ${extension}`,
)

let encodedPayload = undefined
if (payload !== undefined) {
encodedPayload = encodeExtensionPayloadFromJson(extension, payload)
}

const pong = await this._beacon.sendPing(encodedENR, extension, encodedPayload)
if (pong) {
this.logger(`PING/PONG successful with ${encodedENR.nodeId}`)
const { DataRadius } = ClientInfoAndCapabilities.deserialize(pong.customPayload)
return {
enrSeq: Number(pong.enrSeq),
dataRadius: bigIntToHex(DataRadius),
payloadType: pong.payloadType,
payload: decodeExtensionPayloadToJson(pong.payloadType, pong.customPayload),
}
} else {
this.logger(`PING/PONG with ${encodedENR.nodeId} was unsuccessful`)
Expand Down Expand Up @@ -1180,7 +1217,7 @@ export class portal {

// other

async beaconOptimisticStateRoot(params: []): Promise<string> {
async beaconOptimisticStateRoot(): Promise<string> {
this.logger(`beaconOptimisticStateRoot request received`)
if (
this._beacon.lightClient?.status === RunStatusCode.uninitialized ||
Expand All @@ -1192,14 +1229,14 @@ export class portal {
}
throw error
}
const res = await this._beacon.lightClient?.getHead()
const res = this._beacon.lightClient?.getHead()
this.logger(
`beaconOptimisticStateRoot request returned ${res !== undefined ? bytesToHex(res?.beacon.stateRoot) : '0x'}`,
)
return res !== undefined ? bytesToHex(res?.beacon.stateRoot) : '0x'
}

async beaconFinalizedStateRoot(params: []): Promise<string> {
async beaconFinalizedStateRoot(): Promise<string> {
this.logger(`beaconFinalizedStateRoot request received`)
if (
this._beacon.lightClient?.status === RunStatusCode.uninitialized ||
Expand All @@ -1211,7 +1248,7 @@ export class portal {
}
throw error
}
const res = await this._beacon.lightClient?.getFinalized()
const res = this._beacon.lightClient?.getFinalized()
this.logger(
`beaconFinalizedStateRoot request returned ${res !== undefined ? bytesToHex(res?.beacon.stateRoot) : '0x'}`,
)
Expand Down
21 changes: 19 additions & 2 deletions packages/cli/src/rpc/modules/ultralight.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { hexToBytes } from '@ethereumjs/util'
import { HistoryNetworkContentType, NetworkId, addRLPSerializedBlock } from 'portalnetwork'
import { bytesToHex, hexToBytes } from '@ethereumjs/util'
import { HistoryNetworkContentType, NetworkId } from 'portalnetwork'

import { INTERNAL_ERROR } from '../error-code.js'
import { middleware, validators } from '../validators.js'
Expand All @@ -17,6 +17,7 @@ const methods = [
'ultralight_getNetworkDBSize',
'ultralight_pruneNetworkDB',
'ultralight_setNetworkStorage',
'ultralight_getPingPayload',
]

export class ultralight {
Expand Down Expand Up @@ -59,6 +60,10 @@ export class ultralight {
[validators.networkId],
[validators.megabytes],
])
this.getPingPayload = middleware(this.getPingPayload.bind(this), 1, [
[validators.networkId],
[validators.extension],
])
}
async methods() {
return methods
Expand Down Expand Up @@ -191,4 +196,16 @@ export class ultralight {
radius: '0x' + network.nodeRadius.toString(16),
}
}
async getPingPayload(params: [NetworkId, number]) {
const [networkId, extension] = params
const network = this._client.networks.get(networkId)
if (!network) {
throw {
code: INTERNAL_ERROR,
message: `Invalid network id ${networkId}`,
}
}
const payload = bytesToHex(network.pingPongPayload(extension))
return payload
}
}
31 changes: 30 additions & 1 deletion packages/cli/src/rpc/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bytesToHex } from '@ethereumjs/util'
import { HistoryNetworkContentType, NetworkId } from 'portalnetwork'
import { HistoryNetworkContentType, NetworkId, PingPongCustomPayload } from 'portalnetwork'

import { isValidEnr } from '../util.js'

Expand Down Expand Up @@ -506,4 +506,33 @@ export const validators = {
}
}
},

get extension() {
return (params: any[], index: number) => {
if (typeof params[index] !== 'number') {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: argument must be a number`,
}
}
if (!(params[index] in PingPongCustomPayload)) {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: argument must be a supported PingPong payload type`,
}
}
}
},

get payload() {
return (params: any[], index: number) => {
if (typeof params[index] !== 'object') {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: argument must be an object`,
}
}
}
},
}

14 changes: 11 additions & 3 deletions packages/cli/test/rpc/beacon/getLightClientUpdate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ import {
computeLightClientKeyFromPeriod,
getBeaconContentKey,
} from 'portalnetwork'
import { assert, describe, it } from 'vitest'
import { afterAll, assert, beforeAll, describe, it } from 'vitest'

import { startRpc } from '../util.js'
const method = 'beacon_getLightClientUpdate'
describe(`${method} tests`, () => {
let ultralight, rpc

beforeAll(async () => {
;({ ultralight, rpc } = await startRpc({ networks: ['beacon'], rpcPort: 8548, port: 9003 }))
})

afterAll(() => {
ultralight.kill(9)
})

it('should retrieve a light client update', async () => {
const { ultralight, rpc } = await startRpc()
const rangeJson = require('./range.json')[0]
const rangeKey = getBeaconContentKey(
BeaconNetworkContentType.LightClientUpdate,
Expand All @@ -36,6 +45,5 @@ describe(`${method} tests`, () => {
)
const res = await rpc.request(method, [period])
assert.equal(res.result.signature_slot, '7807053')
ultralight.kill(9)
}, 10000)
})
35 changes: 25 additions & 10 deletions packages/cli/test/rpc/portal/beaconLocalContent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,33 @@ import {
LightClientOptimisticUpdateKey,
getBeaconContentKey,
} from 'portalnetwork'
import { assert, describe, it } from 'vitest'
import { afterAll, assert, beforeAll, describe, it } from 'vitest'

import { startRpc } from '../util.js'
const method = 'portal_beaconLocalContent'
describe(`${method} tests`, () => {
it('should not find any local content', async () => {
const { ultralight, rpc } = await startRpc()
const key = LightClientOptimisticUpdateKey.serialize({ signatureSlot: 7807053n })
const res = await rpc.request(method, [
bytesToHex(getBeaconContentKey(BeaconNetworkContentType.LightClientOptimisticUpdate, key)),
])
assert.equal(res.error.code, -32009)
ultralight.kill(9)
}, 10000)
describe(`${method} tests`, () => {
let ul
let rp
beforeAll(async () => {
const { ultralight, rpc } = await startRpc({
networks: ['beacon'],
rpcPort: 8547,
port: 9002,
})
ul = ultralight
rp = rpc
})
it('should not find any local content', async () => {
const key = LightClientOptimisticUpdateKey.serialize({ signatureSlot: 7807053n })
const res = await rp.request(method, [
bytesToHex(getBeaconContentKey(BeaconNetworkContentType.LightClientOptimisticUpdate, key)),
])
console.log(res)
assert.equal(res.error.code, -32009)
}, 10000)
afterAll(() => {
ul.kill()
})
})
})
36 changes: 36 additions & 0 deletions packages/cli/test/rpc/portal/historyPing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { afterAll, assert, beforeAll, describe, it } from 'vitest'

import { startRpc } from '../util.js'
const method = 'portal_historyPing'
describe(`${method} tests`, () => {
let ul
let ul2
let rp
let rp2
beforeAll(async () => {
const { ultralight, rpc } = await startRpc({ networks: ['history'], rpcPort: 8545 })
const { ultralight: ultralight2, rpc: rpc2 } = await startRpc({
port: 9001,
rpcPort: 8546,
networks: ['history'],
})
ul = ultralight
ul2 = ultralight2
rp = rpc
rp2 = rpc2
})
it('should get pong response', async () => {
const enr = (await rp2.request('portal_historyNodeInfo', [])).result.enr
assert.exists(enr)
const res = await rp.request(method, [
enr,
0,
{ ClientInfo: 'ultralight', DataRadius: 1, Capabilities: [0] },
])
assert.equal(res.result.payload.ClientInfo.clientName, 'ultralight')
}, 20000)
afterAll(() => {
ul.kill()
ul2.kill()
})
})
2 changes: 1 addition & 1 deletion packages/cli/test/rpc/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const startRpc = async (opts?: cliConfig) => {
ultralight.on('message', (msg) => {
console.log(msg.toString())
})
const rpc = Client.http({ host: ip, port: 8545 })
const rpc = Client.http({ host: ip, port: opts?.rpcPort })
let done = false
while (!done) {
try {
Expand Down
Loading