diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..accc1b8 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +# ⚠️ IMPORTANT ⚠️ + +# Please do not create a Pull Request for this repository + +The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync. + +Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead. + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) +2. Create your Feature Branch (`git checkout -b feature/amazing-example`) +3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`) +4. Push to the Branch (`git push origin feature/amazing-example`) +5. Open a Pull Request diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 0000000..78f6c8d --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,19 @@ +name: pull + +on: + workflow_dispatch + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Pull from another repository + uses: ipfs-examples/actions-pull-directory-from-repo@main + with: + source-repo: libp2p/js-libp2p-examples + source-folder-path: examples/${{ github.event.repository.name }} + source-branch: main + target-branch: main + git-username: github-actions + git-email: github-actions@github.com diff --git a/1.js b/1.js new file mode 100644 index 0000000..008609d --- /dev/null +++ b/1.js @@ -0,0 +1,49 @@ +/* eslint-disable no-console */ + +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { identify, identifyPush } from '@libp2p/identify' +import { kadDHT, removePublicAddressesMapper } from '@libp2p/kad-dht' +import { tcp } from '@libp2p/tcp' +import { createLibp2p } from 'libp2p' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [tcp()], + streamMuxers: [yamux()], + connectionEncrypters: [noise()], + services: { + // configure Kad-DHT to run on the local network + dht: kadDHT({ + protocol: '/ipfs/lan/kad/1.0.0', + peerInfoMapper: removePublicAddressesMapper, + clientMode: false + }), + identify: identify(), + identifyPush: identifyPush() + } + }) + + return node +} + +const [node1, node2, node3] = await Promise.all([ + createNode(), + createNode(), + createNode() +]) + +// Connect the nodes 1 -> 2 -> 3 +await Promise.all([ + node1.dial(node2.getMultiaddrs()), + node2.dial(node3.getMultiaddrs()) +]) + +// find peer 3 from peer 1 (there is no direct connection) +const peer = await node1.peerRouting.findPeer(node3.peerId) + +console.log('Found it, multiaddrs are:') +peer.multiaddrs.forEach((ma) => console.log(ma.toString())) diff --git a/2.js b/2.js new file mode 100644 index 0000000..2a9e34d --- /dev/null +++ b/2.js @@ -0,0 +1,52 @@ +/* eslint-disable no-console */ + +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { identify, identifyPush } from '@libp2p/identify' +import { kadDHT, removePublicAddressesMapper } from '@libp2p/kad-dht' +import { tcp } from '@libp2p/tcp' +import all from 'it-all' +import { createLibp2p } from 'libp2p' +import { CID } from 'multiformats/cid' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [tcp()], + streamMuxers: [yamux()], + connectionEncrypters: [noise()], + services: { + dht: kadDHT({ + protocol: '/ipfs/lan/kad/1.0.0', + peerInfoMapper: removePublicAddressesMapper, + clientMode: false + }), + identify: identify(), + identifyPush: identifyPush() + } + }) + + return node +} + +const [node1, node2, node3] = await Promise.all([ + createNode(), + createNode(), + createNode() +]) + +await Promise.all([ + node1.dial(node2.getMultiaddrs()), + node2.dial(node3.getMultiaddrs()) +]) + +const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') +await node1.contentRouting.provide(cid) + +console.log('Node %s is providing %s', node1.peerId.toString(), cid.toString()) + +const providers = await all(node3.contentRouting.findProviders(cid, { timeout: 3000 })) + +console.log('Found provider:', providers[0].id.toString()) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..582b533 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# @libp2p/example-peer-and-content-routing + +[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) +[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io) +[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-examples.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-examples) +[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-examples/ci.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain) + +> How to use peer and content routing + +Peer Routing is the category of modules that offer a way to find other peers in the network by intentionally issuing queries, iterative or recursive, until a Peer is found or the closest Peers, given the Peer Routing algorithm strategy are found. + +Content Routing is the category of modules that offer a way to find where content lives in the network, it works in two steps: 1) Peers provide (announce) to the network that they are holders of specific content (multihashes) and 2) Peers issue queries to find where that content lives. A Content Routing mechanism could be as complex as a Kademlia DHT or a simple registry somewhere in the network. + +## 1. Using Peer Routing to find other peers + +This example builds on top of the [Protocol and Stream Muxing](../protocol-and-stream-muxing). We need to install `@libp2p/kad-dht`, go ahead and `npm install @libp2p/kad-dht`. If you want to see the final version, open [1.js](./1.js). + +First, let's update our config to support Peer Routing and Content Routing. + +```JavaScript +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' +import { identify, identifyPush } from '@libp2p/identify' +import { kadDHT, removePublicAddressesMapper } from '@libp2p/kad-dht' +import { tcp } from '@libp2p/tcp' +import { createLibp2p } from 'libp2p' + +const createNode = async () => { + const node = await createLibp2p({ + addresses: { + listen: ['/ip4/0.0.0.0/tcp/0'] + }, + transports: [tcp()], + streamMuxers: [yamux()], + connectionEncrypters: [noise()], + services: { + // configure Kad-DHT to run on the local network + dht: kadDHT({ + protocol: '/ipfs/lan/kad/1.0.0', + peerInfoMapper: removePublicAddressesMapper, + clientMode: false + }), + identify: identify(), + identifyPush: identifyPush() + } + }) + + return node +} +``` + +Once that is done, we can use the createNode function we developed in the previous example to create 3 nodes. Connect node 1 to node 2 and node 2 to node 3. We will use node 2 as a way to find the whereabouts of node 3 + +```JavaScript +const [node1, node2, node3] = await Promise.all([ + createNode(), + createNode(), + createNode() +]) + +await Promise.all([ + node1.dial(node2.getMultiaddrs()), + node2.dial(node3.getMultiaddrs()) +]) + +const peer = await node1.peerRouting.findPeer(node3.peerId) + +console.log('Found it, multiaddrs are:') +peer.multiaddrs.forEach((ma) => console.log(ma.toString())) +``` + +You should see the output being something like: + +```Bash +> node 1.js +Found it, multiaddrs are: +/ip4/127.0.0.1/tcp/63617 +/ip4/192.168.86.41/tcp/63617 +``` + +You have successfully used Peer Routing to find a peer that you were not directly connected. Now all you have to do is to dial to the multiaddrs you discovered. + +## 2. Using Content Routing to find providers of content + +With Content Routing, you can create records that are stored in multiple points in the network, these records can be resolved by you or other peers and they act as memos or rendezvous points. A great usage of this feature is to support discovery of content, where one node holds a file and instead of using a centralized tracker to inform other nodes that it holds that file, it simply puts a record in the network that can be resolved by other peers. Peer Routing and Content Routing are commonly known as Distributed Hash Tables, DHT. + +You can find this example completed in [2.js](./2.js), however as you will see it is very simple to update the previous example. + +Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide` and `contentRouting.findProviders`. + +```JavaScript +import { CID } from 'multiformats/cid' +import all from 'it-all' + +const cid = CID.parse('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL') +await node1.contentRouting.provide(cid) + +console.log('Node %s is providing %s', node1.peerId.toString(), cid.toString()) + +const providers = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 })) + +console.log('Found provider:', providers[0].id.toString()) +``` + +The output of your program should look like: + +```bash +> node 2.js +Node QmSsmVPoTy3WpzwiNPnsKmonBaZjK2HitFs2nWUvwK31Pz is providing QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL +Found provider: QmSsmVPoTy3WpzwiNPnsKmonBaZjK2HitFs2nWUvwK31Pz +``` + +That's it, now you know how to find peers that have pieces of information that interest you! + +## License + +Licensed under either of + +- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](LICENSE-MIT) / ) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/package.json b/package.json new file mode 100644 index 0000000..b5bc489 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "@libp2p/example-peer-and-content-routing", + "version": "0.0.0", + "description": "How to use peer and content routing", + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p-examples/tree/master/examples/js-libp2p-example-peer-and-content-routing#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p-examples.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p-examples/issues" + }, + "type": "module", + "scripts": { + "lint": "aegir lint", + "test": "test-node-example test/*" + }, + "dependencies": { + "@chainsafe/libp2p-noise": "^16.0.0", + "@chainsafe/libp2p-yamux": "^7.0.0", + "@libp2p/identify": "^3.0.1", + "@libp2p/kad-dht": "^13.0.0", + "@libp2p/tcp": "^10.0.0", + "it-all": "^3.0.2", + "libp2p": "^2.0.0", + "multiformats": "^13.1.1" + }, + "devDependencies": { + "test-ipfs-example": "^1.1.0" + }, + "private": true +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..07acb00 --- /dev/null +++ b/test/index.js @@ -0,0 +1,5 @@ +import { test as test1 } from './test-1.js' +import { test as test2 } from './test-2.js' + +await test1() +await test2() diff --git a/test/test-1.js b/test/test-1.js new file mode 100644 index 0000000..0e42d3e --- /dev/null +++ b/test/test-1.js @@ -0,0 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { waitForOutput } from 'test-ipfs-example/node' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { + process.stdout.write('1.js\n') + + await waitForOutput('Found it, multiaddrs are:', 'node', [path.join(__dirname, '../1.js')], { + cwd: __dirname + }) +} diff --git a/test/test-2.js b/test/test-2.js new file mode 100644 index 0000000..435ad8b --- /dev/null +++ b/test/test-2.js @@ -0,0 +1,13 @@ +import path from 'path' +import { fileURLToPath } from 'url' +import { waitForOutput } from 'test-ipfs-example/node' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export async function test () { + process.stdout.write('2.js\n') + + await waitForOutput('Found provider:', 'node', [path.join(__dirname, '../2.js')], { + cwd: __dirname + }) +}