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

PortalNetwork: Deal with rough edges in network startup and CLI Support for Portal Network Node #704

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
171 changes: 171 additions & 0 deletions packages/cli/scripts/portalClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { SignableENR } from '@chainsafe/enr'
import { keys } from '@libp2p/crypto'
import { multiaddr } from '@multiformats/multiaddr'
import { UltralightProvider } from '../../portalnetwork/src/client/provider'
import { TransportLayer, NetworkId } from '../../portalnetwork/src/index'
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'

const args = await yargs(hideBin(process.argv))
.option('method', {
describe: 'Portal Network method to call',
type: 'string',
required: true,
})
.option('params', {
describe: 'Parameters for the method (JSON string)',
type: 'string',
default: '[]',
})
.option('port', {
describe: 'Port number for the node',
type: 'number',
default: 9090,
})
.example('$0 --method portal_statePing --params "[\\"enr:-...\\"]"', 'Ping a state network node')
.strict()
.argv


const NETWORK_IDS = {
STATE: '0x500a',
HISTORY: '0x500b',
BEACON: '0x500c',
}

const MESSAGE_TYPES = {
PING: 0,
PONG: 1,
FINDNODES: 2,
NODES: 3,
TALKREQ: 4,
TALKRESP: 5,
}

async function createNode(port: number): Promise<UltralightProvider> {
const privateKey = await keys.generateKeyPair('secp256k1')
const enr = SignableENR.createFromPrivateKey(privateKey)
const nodeAddr = multiaddr(`/ip4/127.0.0.1/udp/${port}`)
enr.setLocationMultiaddr(nodeAddr)

const node = await UltralightProvider.create({
transport: TransportLayer.NODE,
supportedNetworks: [
{ networkId: NetworkId.HistoryNetwork },
{ networkId: NetworkId.StateNetwork },
],
config: {
enr,
bindAddrs: { ip4: nodeAddr },
privateKey,
},

})

return node
}

async function sendNetworkMessage(node: UltralightProvider, networkId: NetworkId, messageType: number, payload: any = {}): Promise<any> {
console.log(`Sending message type ${messageType} to network ${networkId}:`, payload)

await new Promise(resolve => setTimeout(resolve, 5000))

const serializedPayload = new TextEncoder().encode(JSON.stringify({
type: messageType,
...payload
}))

try {
const response = await node.portal.sendPortalNetworkMessage(
node.portal.discv5.enr.toENR(),
serializedPayload,
networkId
)
return response
} catch (error) {
console.error('Failed to send network message:', error)
throw error
}
}

async function executeMethod(node: UltralightProvider, method: string, params: any[]) {
try {
const [prefix, methodName] = method.split('_')

if (prefix === 'portal') {

if (methodName === 'statePing') {
return await sendNetworkMessage(node, NETWORK_IDS.STATE as NetworkId, MESSAGE_TYPES.PING)
} else if (methodName === 'historyPing') {
return await sendNetworkMessage(node, NETWORK_IDS.HISTORY as NetworkId, MESSAGE_TYPES.PING)
}

const historyNetwork = node.portal.network()[NetworkId.HistoryNetwork]
const stateNetwork = node.portal.network()[NetworkId.StateNetwork]

if (historyNetwork && methodName.startsWith('history')) {
const networkMethod = methodName.replace('history', '').toLowerCase()
if (typeof historyNetwork[networkMethod] === 'function') {
return await historyNetwork[networkMethod](...params)
}
}

if (stateNetwork && methodName.startsWith('state')) {
const networkMethod = methodName.replace('state', '').toLowerCase()
if (typeof stateNetwork[networkMethod] === 'function') {
return await stateNetwork[networkMethod](...params)
}
}

if (typeof node.portal[methodName] === 'function') {
return await node.portal[methodName](...params)
}

throw new Error(`Unknown method: ${methodName}`)
}

throw new Error(`Invalid method prefix: ${prefix}. Must be 'portal'`)
} catch (error) {
console.error('Error executing method:', error)
throw error
}
}

async function main() {
let node: UltralightProvider | undefined
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's replace this with a pure PortalNetwork client instead of the UltralightProvider since you're not accessing the provider methods.

try {
console.log('Creating Portal Network node...')
node = await createNode(args.port)

console.log('Starting Portal Network node...')
await node.portal.start()

console.log('Waiting for node to be ready...')
await new Promise(resolve => setTimeout(resolve, 5000))

console.log(`Node started on port ${args.port}`)

node.portal.enableLog('*Portal*,*uTP*,*discv5*')

node.portal.on('SendTalkReq', (nodeId, requestId, payload) =>
console.log('Sent talk request:', { nodeId, requestId, payload }))
node.portal.on('SendTalkResp', (nodeId, requestId, payload) =>
console.log('Received talk response:', { nodeId, requestId, payload }))

const params = JSON.parse(args.params)
await executeMethod(node, args.method, params)

process.on('SIGINT', async () => {
console.log('Shutting down node...')
await node?.portal.stop()
process.exit(0)
})

} catch (error) {
console.error('Error:', error)
await node?.portal?.stop?.()
process.exit(1)
}
}

main().catch(console.error)
50 changes: 50 additions & 0 deletions packages/portalnetwork/EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Starting a Portal Network Client
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really interesting idea but we need to have the example script in portalnetwork for this to make sense.

So, for starters, let's move your portalClient.ts script from packages/cli/scripts to a new packages/portalnetwork/examples directory.

Second, since your code is instantiating an UltralightProvider instead of a pure portal network client, can you invoke the provider.request method instead of accessing the provider.portal APIs?

Otherwise, can you rewrite the script so it instantiates a pure Portal Network client via PortalNetwork.create instead of the provider?


This describes the usage and functionality of the Portal Network client script, which enables interaction with Portal Network nodes.

## Usage

The script is invoked using the following format:

```bash
npx tsx examples/src/index.ts --method <method_name> --params <json_params> [--port <port_number>]
```

### Options

| Option | Description | Type | Default |
|------------|------------------------------------------|---------|-----------|
| `--method` | Portal Network method to call | String | Required |
| `--params` | Parameters for the method (as JSON) | String | `[]` |
| `--port` | Port number for the node | Number | `9090` |

---


### Supported Networks

The client supports two Portal Network types:
- State Network (0x500a)
- History Network (0x500b)

### Message Types

The following message types are supported:
- PING
- PONG
- FINDNODES
- NODES
- TALKREQ
- TALKRESP

## Examples

1. Store data in history network:
```bash
npx tsx examples/src/index.ts --method portal_historyStore --params '["hello world"]'
```

2. Custom port configuration:
```bash
npx tsx examples/src/index.ts --method portal_statePing --params '["enr:-..."]' --port 9091
```
4 changes: 3 additions & 1 deletion packages/portalnetwork/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

A Typescript library for interacting with the Portal Network

See [API](./docs/modules.md) for more details
See [API](./docs/modules.html) for more details

See [Architecture](./diagrams/ARCHITECTURE.md) for architectural concepts

See [Examples](./EXAMPLES.md) for different ways to interact with Portal Network nodes.

## Routing Table Management

The Portal Network module currently supports two overlay routing tables for use with the below two subnetworks:
Expand Down
36 changes: 36 additions & 0 deletions packages/portalnetwork/examples/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'

export interface PortalConfig {
method: string
params: string
port: number
}

export const defaultConfig: PortalConfig = {
method: '',
params: '[]',
port: 9090,
}

export function parseArgs(argv: string[]): Promise<PortalConfig> {
return yargs(hideBin(argv))
.option('method', {
describe: 'Portal Network method to call',
type: 'string',
required: true,
})
.option('params', {
describe: 'Parameters for the method (JSON string)',
type: 'string',
default: '[]',
})
.option('port', {
describe: 'Port number for the node',
type: 'number',
default: 9090,
})
.example('$0 --method portal_statePing --params "[\\"enr:-...\\"]"', 'Ping a state network node')
.strict()
.parse() as Promise<PortalConfig>
}
14 changes: 14 additions & 0 deletions packages/portalnetwork/examples/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { parseArgs } from './config.js'
import { runPortalClient } from './portalClient.js'

export async function main() {
try {
const config = await parseArgs(process.argv)
await runPortalClient(config)
} catch (error) {
console.error('Error:', error)
process.exit(1)
}
}

main().catch(console.error)
Loading
Loading