Skip to content

Commit

Permalink
RPC: update portal_*Offer (#636)
Browse files Browse the repository at this point in the history
* RPC: add contentItem(s) validator

* RPC: update portal_*Offer for multiples

* network: ensure all or none content is provided to Offer

* update method calls in script

* beacon: refactor sendOffer to accept optional contents array

* RPC: implement portal_beaconOffer
  • Loading branch information
ScottyPoi authored Oct 3, 2024
1 parent f5d0ecb commit a4f15bb
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 46 deletions.
24 changes: 14 additions & 10 deletions packages/cli/scripts/seeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,20 @@ const main = async () => {
[
clientInfo.peer1.enr,
getContentKey(HistoryNetworkContentType.BlockHeader, fromHexString(block[0])),
toHexString(
Block.fromRLPSerializedBlock(hexToBytes((block[1] as any).rlp), {
setHardfork: true,
}).header.serialize(),
),
toHexString(
Block.fromRLPSerializedBlock(hexToBytes((block[1] as any).rlp), {
setHardfork: true,
}).header.serialize(),
),
[
[
toHexString(
Block.fromRLPSerializedBlock(hexToBytes((block[1] as any).rlp), {
setHardfork: true,
}).header.serialize(),
),
toHexString(
Block.fromRLPSerializedBlock(hexToBytes((block[1] as any).rlp), {
setHardfork: true,
}).header.serialize(),
),
],
],
],
])
}
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/scripts/sendOffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ const main = async () => {

const offer = await nodeA.request('portal_historyOffer', [
nodeBEnr.result.enr,
blockBodyContent_key,
blockBodyContent_value,
[[blockBodyContent_key, blockBodyContent_value]],
])

console.log(offer)
Expand Down
52 changes: 33 additions & 19 deletions packages/cli/src/rpc/modules/portal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EntryStatus } from '@chainsafe/discv5'
import { ENR } from '@chainsafe/enr'
import { BitArray } from '@chainsafe/ssz'
import { bytesToHex, short } from '@ethereumjs/util'
import { hexToBytes, short } from '@ethereumjs/util'
import {
ContentLookup,
ContentMessageType,
Expand All @@ -17,6 +17,7 @@ import {
} from 'portalnetwork'

import { INVALID_PARAMS } from '../error-code.js'
import { content_params } from '../schema/index.js'
import { isValidId } from '../util.js'
import { middleware, validators } from '../validators.js'

Expand Down Expand Up @@ -84,6 +85,7 @@ const methods = [
'portal_beaconGetEnr',
'portal_beaconDeleteEnr',
'portal_beaconLookupEnr',
'portal_beaconOffer',

// not included in portal-network-specs
'portal_historyAddEnrs',
Expand Down Expand Up @@ -186,17 +188,19 @@ export class portal {
])
this.historyOffer = middleware(this.historyOffer.bind(this), 3, [
[validators.enr],
[validators.hex],
[validators.hex],
[content_params.ContentItems],
])
this.beaconOffer = middleware(this.beaconOffer.bind(this), 3, [
[validators.enr],
[content_params.ContentItems],
])
this.historySendOffer = middleware(this.historySendOffer.bind(this), 2, [
[validators.dstId],
[validators.array(validators.hex)],
])
this.stateOffer = middleware(this.stateOffer.bind(this), 3, [
[validators.enr],
[validators.hex],
[validators.hex],
[content_params.ContentItems],
])
this.stateSendOffer = middleware(this.stateSendOffer.bind(this), 2, [
[validators.dstId],
Expand Down Expand Up @@ -802,20 +806,32 @@ export class portal {
}
}
}
async historyOffer(params: [string, string, string]) {
const [enrHex, contentKeyHex, contentValueHex] = params
async historyOffer(params: [string, [string, string][]]) {
const [enrHex, contentItems] = params
const contentKeys = contentItems.map((item) => hexToBytes(item[0]))
const contentValues = contentItems.map((item) => hexToBytes(item[1]))
const enr = ENR.decodeTxt(enrHex)
if (this._history.routingTable.getWithPending(enr.nodeId)?.value === undefined) {
const res = await this._history.sendPing(enr)
if (res === undefined) {
return '0x'
}
}
const res = await this._history.sendOffer(
enr.nodeId,
[fromHexString(contentKeyHex)],
[fromHexString(contentValueHex)],
)
const res = await this._history.sendOffer(enr.nodeId, contentKeys, contentValues)
return res
}
async beaconOffer(params: [string, [string, string][]]) {
const [enrHex, contentItems] = params
const contentKeys = contentItems.map((item) => hexToBytes(item[0]))
const contentValues = contentItems.map((item) => hexToBytes(item[1]))
const enr = ENR.decodeTxt(enrHex)
if (this._beacon.routingTable.getWithPending(enr.nodeId)?.value === undefined) {
const res = await this._beacon.sendPing(enr)
if (res === undefined) {
return '0x'
}
}
const res = await this._beacon.sendOffer(enr.nodeId, contentKeys, contentValues)
return res
}
async historySendOffer(params: [string, string[]]) {
Expand All @@ -825,20 +841,18 @@ export class portal {
const enr = this._history.routingTable.getWithPending(dstId)?.value
return res && enr && '0x' + enr.seq.toString(16)
}
async stateOffer(params: [string, string, string]) {
const [enrHex, contentKeyHex, contentValueHex] = params
async stateOffer(params: [string, [string, string][]]) {
const [enrHex, contentItems] = params
const contentKeys = contentItems.map((item) => fromHexString(item[0]))
const contentValues = contentItems.map((item) => fromHexString(item[1]))
const enr = ENR.decodeTxt(enrHex)
if (this._state.routingTable.getWithPending(enr.nodeId)?.value === undefined) {
const res = await this._state.sendPing(enr)
if (res === undefined) {
return '0x'
}
}
const res = await this._state.sendOffer(
enr.nodeId,
[fromHexString(contentKeyHex)],
[fromHexString(contentValueHex)],
)
const res = await this._state.sendOffer(enr.nodeId, contentKeys, contentValues)
return res
}
async stateSendOffer(params: [string, string[]]) {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/rpc/schema/baseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ export const baseTypes = {
}
},
/**
* bytes2 validator to ensure has `0x` prefix and 66 bytes length
* hex string validator to ensure has `0x` prefix
* @param params parameters of method
* @param index index of parameter
*/
get hexString() {
return (params: any[], index: number) => {
return validateByteString(params[index], index, 66)
return validateByteString(params[index], index)
}
},
}
46 changes: 46 additions & 0 deletions packages/cli/src/rpc/schema/content/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,50 @@ export const content_params = {
if (result !== undefined) return result
}
},
get ContentItem() {
return (params: any[], index: number) => {
if (!Array.isArray(params[index])) {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: argument is not an array`,
}
}
if (params[index].length !== 2) {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: array length is not 2`,
}
}
const [key, value] = params[index]
const keyResult = content_params.ContentKey([key], 0)
const valueResult = content_params.ContentValue([value], 1)
if (keyResult !== undefined && valueResult !== undefined) return [keyResult, valueResult]
}
},
get ContentItems() {
return (params: any[], index: number) => {
if (!Array.isArray(params[index])) {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: argument is not an array`,
}
}
if (params[index].length < 1) {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: array is empty`,
}
}
if (params[index].length > 64) {
return {
code: INVALID_PARAMS,
message: `invalid argument ${index}: array is too long`,
}
}
for (const value of params[index]) {
const result = content_params.ContentItem([value], 0)
if (result !== undefined) return result
}
}
},
}
41 changes: 28 additions & 13 deletions packages/portalnetwork/src/networks/beacon/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,14 @@ export class BeaconLightClientNetwork extends BaseNetwork {
* @param dstId node ID of a peer
* @param contentKeys content keys being offered as specified by the subnetwork
*/
public override sendOffer = async (dstId: string, contentKeys: Uint8Array[]) => {
public override sendOffer = async (
dstId: string,
contentKeys: Uint8Array[],
contents?: Uint8Array[],
) => {
if (contents && contents.length !== contentKeys.length) {
throw new Error('Provided Content and content key arrays must be the same length')
}
if (contentKeys.length > 0) {
this.portal.metrics?.offerMessagesSent.inc()
const offerMsg: OfferMessage = {
Expand Down Expand Up @@ -806,27 +813,35 @@ export class BeaconLightClientNetwork extends BaseNetwork {
this.logger.extend(`ACCEPT`)(`ACCEPT message received with uTP id: ${id}`)

const requestedData: Uint8Array[] = []
for await (const key of requestedKeys) {
let value = Uint8Array.from([])
try {
// We use `findContentLocally` instead of `get` so the content keys for
// optimistic and finality updates are handled correctly
value = (await this.findContentLocally(key)) as Uint8Array
requestedData.push(value)
} catch (err: any) {
this.logger(`Error retrieving content -- ${err.toString()}`)
requestedData.push(value)
if (contents) {
for (const [idx, _] of requestedKeys.entries()) {
if (msg.contentKeys.get(idx) === true) {
requestedData.push(contents[idx])
}
}
} else {
for await (const key of requestedKeys) {
let value = Uint8Array.from([])
try {
// We use `findContentLocally` instead of `get` so the content keys for
// optimistic and finality updates are handled correctly
value = (await this.findContentLocally(key)) as Uint8Array
requestedData.push(value)
} catch (err: any) {
this.logger(`Error retrieving content -- ${err.toString()}`)
requestedData.push(value)
}
}
}

const contents = encodeWithVariantPrefix(requestedData)
const encoded = encodeWithVariantPrefix(requestedData)
await this.handleNewRequest({
networkId: this.networkId,
contentKeys: requestedKeys,
peerId: dstId,
connectionId: id,
requestCode: RequestCode.OFFER_WRITE,
contents: [contents],
contents: [encoded],
})

return msg.contentKeys
Expand Down
3 changes: 3 additions & 0 deletions packages/portalnetwork/src/networks/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ export abstract class BaseNetwork extends EventEmitter {
* @param networkId network ID of subnetwork being used
*/
public sendOffer = async (dstId: string, contentKeys: Uint8Array[], content?: Uint8Array[]) => {
if (content && content.length !== contentKeys.length) {
throw new Error('Must provide all content or none')
}
if (contentKeys.length > 0) {
this.portal.metrics?.offerMessagesSent.inc()
const offerMsg: OfferMessage = {
Expand Down

0 comments on commit a4f15bb

Please sign in to comment.