-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: master
Are you sure you want to change the base?
PortalNetwork: Deal with rough edges in network startup and CLI Support for Portal Network Node #704
Changes from all commits
f233778
46269a2
049bcf9
f9dc1a0
5f416da
6bab600
4d24ae6
ce1d95a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Starting a Portal Network Client | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 So, for starters, let's move your Second, since your code is instantiating an Otherwise, can you rewrite the script so it instantiates a pure Portal Network client via |
||
|
||
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 | ||
``` |
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> | ||
} |
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) |
There was a problem hiding this comment.
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 theUltralightProvider
since you're not accessing the provider methods.