From 742ea7f70902d5cb20dc180dd3303263ca4bdeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eliel=20=EC=88=98=ED=95=99?= <46634774+elielnfinic@users.noreply.github.com> Date: Wed, 23 Oct 2024 07:12:40 +0200 Subject: [PATCH] improv: local bootstrap node set up w/ example (#200) Co-authored-by: droak --- examples/local-bootstrap/.gitignore | 4 + examples/local-bootstrap/README.md | 29 +++++ examples/local-bootstrap/asconfig.json | 27 +++++ examples/local-bootstrap/index.html | 44 ++++++++ examples/local-bootstrap/package.json | 35 ++++++ examples/local-bootstrap/src/index.ts | 120 +++++++++++++++++++++ examples/local-bootstrap/tsconfig.json | 14 +++ examples/local-bootstrap/vite.config.mts | 24 +++++ packages/network/src/node.ts | 39 ++++--- packages/node/README.md | 13 +++ packages/node/configs/local-bootstrap.json | 8 ++ pnpm-lock.yaml | 52 +++++++++ 12 files changed, 395 insertions(+), 14 deletions(-) create mode 100644 examples/local-bootstrap/.gitignore create mode 100644 examples/local-bootstrap/README.md create mode 100644 examples/local-bootstrap/asconfig.json create mode 100644 examples/local-bootstrap/index.html create mode 100644 examples/local-bootstrap/package.json create mode 100644 examples/local-bootstrap/src/index.ts create mode 100644 examples/local-bootstrap/tsconfig.json create mode 100644 examples/local-bootstrap/vite.config.mts create mode 100644 packages/node/configs/local-bootstrap.json diff --git a/examples/local-bootstrap/.gitignore b/examples/local-bootstrap/.gitignore new file mode 100644 index 00000000..abca009d --- /dev/null +++ b/examples/local-bootstrap/.gitignore @@ -0,0 +1,4 @@ +dist/ +docs/ +node_modules/ +public/static \ No newline at end of file diff --git a/examples/local-bootstrap/README.md b/examples/local-bootstrap/README.md new file mode 100644 index 00000000..b4882cfb --- /dev/null +++ b/examples/local-bootstrap/README.md @@ -0,0 +1,29 @@ +# Topology Protocol Example + +This is an example of Topology Protocol usage in a local environment after a user has run a custom bootstrap node and tries to connect it and test the chat room system. + +## How to run locally + +After cloning the repository, run the following commands: + +```bash +cd ts-topology/examples/local-env/chat +yarn +yarn build +yarn dev +``` + + +In your browser, open the URL displayed in the terminal. You will see a form to connect to the bootstrap node. Copy the `peerId` from the terminal where you started the node and paste it in the form. Click "Connect". +By default, the bootstrap node is running on `127.0.0.1:50000` using `ws` but you can change these values and connect to your node. + + +Debugging is made easier by setting the mode in `webpack.config.js` to "development": + +```js +module.exports = { + mode: "development", + ... +} +``` + diff --git a/examples/local-bootstrap/asconfig.json b/examples/local-bootstrap/asconfig.json new file mode 100644 index 00000000..46e620da --- /dev/null +++ b/examples/local-bootstrap/asconfig.json @@ -0,0 +1,27 @@ +{ + "entries": ["./src/objects/chat.ts"], + "targets": { + "debug": { + "outFile": "dist/asc/debug.wasm", + "textFile": "dist/asc/debug.wat", + "sourceMap": true, + "debug": true + }, + "release": { + "outFile": "dist/asc/release.wasm", + "textFile": "dist/asc/release.wat", + "sourceMap": false, + "optimizeLevel": 3, + "shrinkLevel": 0, + "converge": false, + "noAssert": false + } + }, + "options": { + "lib": { + "@topology-foundation/crdt": [ + "./node_modules/@topology-foundation/blueprints/src/index.asc.ts" + ] + } + } +} diff --git a/examples/local-bootstrap/index.html b/examples/local-bootstrap/index.html new file mode 100644 index 00000000..887b6c52 --- /dev/null +++ b/examples/local-bootstrap/index.html @@ -0,0 +1,44 @@ + + + + + + Topology - Local env + + +
+

Topology Protocol - Local bootstrap connect

+

Test the connection to a boostrap node and connect your local peers.

+

+

+ Connect to a bootstrap node +
+ + + + + + + +
Host address type + +
Port
Peer ID
Web socket protocol + + + + +
+
+
+

+

Connecting to bootstrap node:

+

Current peer ID:

+

peers:

+
+ + + + diff --git a/examples/local-bootstrap/package.json b/examples/local-bootstrap/package.json new file mode 100644 index 00000000..785d82b6 --- /dev/null +++ b/examples/local-bootstrap/package.json @@ -0,0 +1,35 @@ +{ + "name": "topology-example-localdev", + "version": "0.2.0", + "description": "Topology Protocol Local Dev Example", + "main": "src/index.ts", + "repository": "https://github.com/topology-foundation/ts-topology.git", + "license": "MIT", + "scripts": { + "asbuild": "asc --config asconfig.json --target release", + "build": "vite build", + "clean": "rm -rf dist/ node_modules/", + "dev": "vite serve", + "start": "ts-node ./src/index.ts" + }, + "dependencies": { + "@topology-foundation/blueprints": "0.2.0", + "@topology-foundation/network": "0.2.0", + "@topology-foundation/node": "0.2.0", + "@topology-foundation/object": "0.2.0", + "assemblyscript": "^0.27.29", + "crypto-browserify": "^3.12.0", + "process": "^0.11.10", + "stream-browserify": "^3.0.0", + "ts-node": "^10.9.2", + "uint8arrays": "^5.1.0", + "vm-browserify": "^1.1.2" + }, + "devDependencies": { + "@types/node": "^22.5.4", + "ts-loader": "^9.5.1", + "typescript": "^5.5.4", + "vite": "^5.4.6", + "vite-plugin-node-polyfills": "^0.22.0" + } +} diff --git a/examples/local-bootstrap/src/index.ts b/examples/local-bootstrap/src/index.ts new file mode 100644 index 00000000..0e96958a --- /dev/null +++ b/examples/local-bootstrap/src/index.ts @@ -0,0 +1,120 @@ +import { + TopologyNode, + type TopologyNodeConfig, +} from "@topology-foundation/node"; + +const local_peer_id = "12D3KooWC6sm9iwmYbeQJCJipKTRghmABNz1wnpJANvSMabvecwJ"; +// This is the IP of the local bootstrap node, replace it with the IP of the local node +const local_bootstrap_peer_ip = "127.0.0.1"; + +if (!local_peer_id) { + console.error( + "topology::network::start::bootstrap: Set local_peer_id in `/examples/local-bootstrap/src/index.ts` file with the peer id of the local bootstrap node", + ); + process.exit(1); +} + +let node: TopologyNode; +let peers: string[] = []; + +const render = () => { + const element_peerId = document.getElementById("peerId"); + element_peerId.innerHTML = node.networkNode.peerId; + + const element_peers = document.getElementById("peers"); + element_peers.innerHTML = `[${peers.join(", ")}]`; +}; + +async function initTopologyNode() { + if (node) { + node.addCustomGroupMessageHandler("", (e) => { + peers = node.networkNode.getAllPeers(); + render(); + }); + } +} + +async function main() { + const select_address_type = ( + document.getElementById("bootstrap_node_host_address_type") + ); + const address_type_label = ( + document.getElementById("bootstrap_addr_type") + ); + const bootstrap_node_addr = ( + document.getElementById("bootstrap_node_addr") + ); + + // Default to IP4 + select_address_type.value = "ip4"; + address_type_label.innerText = "IP address"; + + select_address_type?.addEventListener("change", (e) => { + const val = select_address_type.value; + if (val === "ip4") { + address_type_label.innerText = "IP address"; + bootstrap_node_addr.placeholder = "0.0.0.0"; + bootstrap_node_addr.value = "127.0.0.1"; + } else if (val === "dns4") { + address_type_label.innerText = "DNS address"; + bootstrap_node_addr.placeholder = "example.com"; + bootstrap_node_addr.value = ""; + } + }); + + const connect_form = ( + document.getElementById("form_connect_to_bootstrap_node") + ); + connect_form?.addEventListener("submit", async (e) => { + e.preventDefault(); + const bootstrap_node_port: HTMLInputElement = ( + document.getElementById("bootstrap_node_port") + ); + const bootstrap_node_peer_id: HTMLInputElement = ( + document.getElementById("bootstrap_node_peer_id") + ); + + if ( + !bootstrap_node_addr.value || + !bootstrap_node_port.value || + !bootstrap_node_peer_id.value + ) { + alert("Please fill in all the fields"); + return; + } + + const is_ws: HTMLInputElement = ( + document.getElementById("ws") + ); + + const ws_protocl = is_ws.checked ? "ws" : "wss"; + const field_set = ( + document.getElementById("fieldset_connect_bootstrap_node") + ); + try { + node = new TopologyNode({ + network_config: { + bootstrap_peers: [ + `/${select_address_type.value}/${bootstrap_node_addr.value}/tcp/${bootstrap_node_port.value}/${ws_protocl}/p2p/${bootstrap_node_peer_id.value}`, + ], + bootstrap: false, + }, + }); + + await node.start(); + field_set.style.backgroundColor = "rgba(0, 255, 0, 0.2)"; + initTopologyNode(); + render(); + } catch (e) { + field_set.style.backgroundColor = "rgba(255, 0, 0, 0.2)"; + alert("Failed to connect to the bootstrap node"); + return; + } + }); + + render(); + + // generic message handler +} + +main(); diff --git a/examples/local-bootstrap/tsconfig.json b/examples/local-bootstrap/tsconfig.json new file mode 100644 index 00000000..23d99aec --- /dev/null +++ b/examples/local-bootstrap/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "rootDir": ".", + "strict": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "allowJs": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/examples/local-bootstrap/vite.config.mts b/examples/local-bootstrap/vite.config.mts new file mode 100644 index 00000000..6696fa36 --- /dev/null +++ b/examples/local-bootstrap/vite.config.mts @@ -0,0 +1,24 @@ +import path from "node:path"; +import { defineConfig } from "vite"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; + +export default defineConfig({ + build: { + target: "esnext", + }, + plugins: [ + nodePolyfills({ + overrides: {}, + }), + ], + optimizeDeps: { + esbuildOptions: { + target: "esnext", + }, + }, + resolve: { + alias: { + "@topology-foundation": path.resolve(__dirname, "../../packages"), + }, + }, +}); diff --git a/packages/network/src/node.ts b/packages/network/src/node.ts index 0bc53594..bef3afb5 100644 --- a/packages/network/src/node.ts +++ b/packages/network/src/node.ts @@ -24,6 +24,7 @@ import type { import { pubsubPeerDiscovery } from "@libp2p/pubsub-peer-discovery"; import { webRTC, webRTCDirect } from "@libp2p/webrtc"; import { webSockets } from "@libp2p/websockets"; +import * as filters from "@libp2p/websockets/filters"; import { webTransport } from "@libp2p/webtransport"; import { multiaddr } from "@multiformats/multiaddr"; import { type Libp2p, createLibp2p } from "libp2p"; @@ -63,6 +64,26 @@ export class TopologyNetworkNode { ); } + const _bootstrapNodesList = this._config?.bootstrap_peers + ? this._config.bootstrap_peers + : [ + "/dns4/relay.droak.sh/tcp/443/wss/p2p/Qma3GsJmB47xYuyahPZPSadh1avvxfyYQwk8R3UnFrQ6aP", + ]; + + const _pubsubPeerDiscovery = pubsubPeerDiscovery({ + interval: 10_000, + topics: ["topology::discovery"], + }); + + const _peerDiscovery = _bootstrapNodesList.length + ? [ + _pubsubPeerDiscovery, + bootstrap({ + list: _bootstrapNodesList, + }), + ] + : [_pubsubPeerDiscovery]; + this._node = await createLibp2p({ privateKey, addresses: { @@ -75,19 +96,7 @@ export class TopologyNetworkNode { }, }, metrics: this._config?.browser_metrics ? devToolsMetrics() : undefined, - peerDiscovery: [ - pubsubPeerDiscovery({ - interval: 10_000, - topics: ["topology::discovery"], - }), - bootstrap({ - list: this._config?.bootstrap_peers - ? this._config.bootstrap_peers - : [ - "/dns4/relay.droak.sh/tcp/443/wss/p2p/Qma3GsJmB47xYuyahPZPSadh1avvxfyYQwk8R3UnFrQ6aP", - ], - }), - ], + peerDiscovery: _peerDiscovery, services: { autonat: autoNAT(), dcutr: dcutr(), @@ -102,7 +111,9 @@ export class TopologyNetworkNode { }), webRTC(), webRTCDirect(), - webSockets(), + webSockets({ + filter: filters.all, + }), webTransport(), ], }); diff --git a/packages/node/README.md b/packages/node/README.md index 87d9f51e..1c01ec96 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -26,6 +26,19 @@ For more information on what are the commands available, run: topology-node --help ``` +#### Running a bootstrap node +You can run a bootstrap node using the following command: + +```bash +pnpm relay --config configs/bootstrap.json +``` + +If you want to run a local bootstrap node, you can use the following command: + +```bash +pnpm relay --config configs/local-bootstrap.json +``` + ### Integration To integrate the Topology Node into an existing application, you can install it using: diff --git a/packages/node/configs/local-bootstrap.json b/packages/node/configs/local-bootstrap.json new file mode 100644 index 00000000..d6477c35 --- /dev/null +++ b/packages/node/configs/local-bootstrap.json @@ -0,0 +1,8 @@ +{ + "network_config": { + "addresses": ["/ip4/0.0.0.0/tcp/50000/ws", "/ip4/0.0.0.0/tcp/50001"], + "bootstrap": true, + "bootstrap_peers": [], + "private_key_seed": "bootstrap" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31b44202..cd66023b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,6 +195,58 @@ importers: specifier: ^0.22.0 version: 0.22.0(rollup@4.24.0)(vite@5.4.9(@types/node@22.7.5)(terser@5.34.1)) + examples/local-bootstrap: + dependencies: + '@topology-foundation/blueprints': + specifier: 0.2.0 + version: link:../../packages/blueprints + '@topology-foundation/network': + specifier: 0.2.0 + version: link:../../packages/network + '@topology-foundation/node': + specifier: 0.2.0 + version: link:../../packages/node + '@topology-foundation/object': + specifier: 0.2.0 + version: link:../../packages/object + assemblyscript: + specifier: ^0.27.29 + version: 0.27.30 + crypto-browserify: + specifier: ^3.12.0 + version: 3.12.0 + process: + specifier: ^0.11.10 + version: 0.11.10 + stream-browserify: + specifier: ^3.0.0 + version: 3.0.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.7.5)(typescript@5.6.2) + uint8arrays: + specifier: ^5.1.0 + version: 5.1.0 + vm-browserify: + specifier: ^1.1.2 + version: 1.1.2 + devDependencies: + '@types/node': + specifier: ^22.5.4 + version: 22.7.5 + ts-loader: + specifier: ^9.5.1 + version: 9.5.1(typescript@5.6.2)(webpack@5.95.0) + typescript: + specifier: ^5.5.4 + version: 5.6.2 + vite: + specifier: ^5.4.6 + version: 5.4.8(@types/node@22.7.5)(terser@5.34.1) + vite-plugin-node-polyfills: + specifier: ^0.22.0 + version: 0.22.0(rollup@4.24.0)(vite@5.4.8(@types/node@22.7.5)(terser@5.34.1)) + packages/blueprints: dependencies: '@thi.ng/random':