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

Delegated Peer and Content Routing #242

Merged
merged 19 commits into from
Oct 19, 2018
Merged
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
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,12 @@ const MulticastDNS = require('libp2p-mdns')
const DHT = require('libp2p-kad-dht')
const defaultsDeep = require('@nodeutils/defaults-deep')
const Protector = require('libp2p-pnet')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')

class Node extends libp2p {
constructor (_options) {
const peerInfo = _options.peerInfo
const defaults = {
// The libp2p modules for this libp2p bundle
modules: {
Expand All @@ -133,8 +136,16 @@ class Node extends libp2p {
connEncryption: [
SECIO
],
// Encryption for private networks. Needs additional private key to work
/** Encryption for private networks. Needs additional private key to work **/
// connProtector: new Protector(/*protector specific opts*/),
/** Enable custom content routers, such as delegated routing **/
// contentRouting: [
// new DelegatedContentRouter(peerInfo.id)
// ],
/** Enable custom peer routers, such as delegated routing **/
// peerRouting: [
// new DelegatedPeerRouter()
// ],
peerDiscovery: [
MulticastDNS
],
Expand Down Expand Up @@ -230,16 +241,19 @@ Required keys in the `options` object:

`callback` is a function with the following `function (err) {}` signature, where `err` is an Error in case stopping the node fails.

#### `libp2p.peerRouting.findPeer(id, callback)`
#### `libp2p.peerRouting.findPeer(id, options, callback)`

> Looks up for multiaddrs of a peer in the DHT

- `id`: instance of [PeerId][]
- `options`: object of options
- `options.maxTimeout`: Number milliseconds

#### `libp2p.contentRouting.findProviders(key, timeout, callback)`
#### `libp2p.contentRouting.findProviders(key, options, callback)`

- `key`: Buffer
- `timeout`: Number miliseconds
- `options`: object of options
- `options.maxTimeout`: Number milliseconds

#### `libp2p.contentRouting.provide(key, callback)`

Expand Down Expand Up @@ -307,14 +321,18 @@ Required keys in the `options` object:
- `key`: Buffer
- `value`: Buffer

#### `libp2p.dht.get(key, callback)`
#### `libp2p.dht.get(key, options, callback)`

- `key`: Buffer
- `options`: object of options
- `options.maxTimeout`: Number milliseconds

#### `libp2p.dht.getMany(key, nVals, callback)`
#### `libp2p.dht.getMany(key, nVals, options, callback)`

- `key`: Buffer
- `nVals`: Number
- `options`: object of options
- `options.maxTimeout`: Number milliseconds

[PeerInfo]: https://github.com/libp2p/js-peer-info
[PeerId]: https://github.com/libp2p/js-peer-id
Expand Down
49 changes: 49 additions & 0 deletions examples/delegated-routing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Delegated Routing with Libp2p and IPFS

This example shows how to use delegated peer and content routing. The [Peer and Content Routing Example](../peer-and-content-routing) focuses
on the DHT implementation. This example takes that a step further and introduces delegated routing. Delegated routing is
especially useful when your libp2p node will have limited resources, making running a DHT impractical. It's
also highly useful if your node is generating content, but can't reliably be on the network. You can use delegate nodes
to provide content on your behalf.

The starting [Libp2p Bundle](./src/libp2p-bundle.js) in this example starts by disabling the DHT and adding the Delegated Peer and Content Routers.
Once you've completed the example, you should try enabled the DHT and see what kind of results you get! You can also enable the
various Peer Discovery modules and see the impact it has on your Peer count.

## Prerequisite
**NOTE**: This example is currently dependent on a clone of the [delegated routing support branch of go-ipfs](https://github.com/ipfs/go-ipfs/pull/4595).

## Running this example

1. Install IPFS locally if you dont already have it. [Install Guide](https://docs.ipfs.io/introduction/install/)
2. Run the IPFS daemon: `ipfs daemon`
3. The daemon will output a line about its API address, like `API server listening on /ip4/127.0.0.1/tcp/8080`
4. In another window output the addresses of the node: `ipfs id`. Make note of the websocket address, is will contain `/ws/` in the address.
5. In `./src/libp2p-bundle.js` replace the `delegatedApiOptions` host and port of your node if they are different.
6. In `./src/App.js` replace `BootstrapNode` with your nodes Websocket address from step 4.
7. Start this example:

```sh
npm install
npm start
```

This should open your browser to http://localhost:3000. If it does not, go ahead and do that now.

8. Your browser should show you connected to at least 1 peer.

### Finding Content via the Delegate
1. Add a file to your IPFS node. From this example root you can do `ipfs add ./README.md` to add the example readme.
2. Copy the hash from line 5, it will look something like *Qmf33vz4HJFkqgH7XPP1uA6atYKTX1BWQEQthzpKcAdeyZ*.
3. In the browser, paste the hash into the *Hash* field and hit `Find`. The readme contents should display.

This will do a few things:
* The delegate nodes api will be queried to find providers of the content
* The content will be fetched from the providers
* Since we now have the content, we tell the delegate node to fetch the content from us and become a provider

### Finding Peers via the Delegate
1. Get a list of your delegate nodes peer by querying the IPFS daemon: `ipfs swarm peers`
2. Copy one of the CIDs from the list of peer addresses, this will be the last portion of the address and will look something like `QmdoG8DpzYUZMVP5dGmgmigZwR1RE8Cf6SxMPg1SBXJAQ8`.
3. In your browser, paste the CID into the *Peer* field and hit `Find`.
4. You should see information about the peer including its addresses.
23 changes: 23 additions & 0 deletions examples/delegated-routing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "delegated-routing-example",
"version": "0.1.0",
"private": true,
"dependencies": {
"ipfs": "~0.32.2",
"libp2p": "../../",
"libp2p-delegated-content-routing": "~0.2.2",
"libp2p-delegated-peer-routing": "~0.2.2",
"libp2p-kad-dht": "~0.10.4",
"libp2p-mplex": "~0.8.0",
"libp2p-secio": "~0.10.0",
"libp2p-webrtc-star": "~0.15.5",
"libp2p-websocket-star": "~0.8.1",
"libp2p-websockets": "~0.12.0",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-scripts": "1.1.5"
},
"scripts": {
"start": "react-scripts start"
}
}
Binary file added examples/delegated-routing/public/favicon.ico
Binary file not shown.
16 changes: 16 additions & 0 deletions examples/delegated-routing/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>Delegated Routing</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
67 changes: 67 additions & 0 deletions examples/delegated-routing/public/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

section * {
margin: 10px;
}

header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}

.center {
text-align: center;
}

pre {
background-color: bisque;
min-height: 100px;
margin: 0px;
padding: 10px;
}

.loader {
text-align: center;
height: 64px;
margin-bottom: -64px;
}

.loading .lds-ripple {
display: inline-block;
position: relative;
width: 64px;
height: 64px;
}
.loading .lds-ripple div {
position: absolute;
border: 4px solid #000;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
margin: auto;
}
.loading .lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 28px;
left: 28px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: -1px;
left: -1px;
width: 58px;
height: 58px;
opacity: 0;
}
}
153 changes: 153 additions & 0 deletions examples/delegated-routing/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// eslint-disable-next-line
'use strict'

const React = require('react')
const Component = React.Component
const Ipfs = require('ipfs')
const libp2pBundle = require('./libp2p-bundle')
// require('./App.css')

const BootstrapNode = '/ip4/127.0.0.1/tcp/8081/ws/ipfs/QmdoG8DpzYUZMVP5dGmgmigZwR1RE8Cf6SxMPg1SBXJAQ8'

class App extends Component {
constructor (props) {
super(props)
this.state = {
peers: 0,
// This hash is the IPFS readme
hash: 'QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB',
// This peer is one of the Bootstrap nodes for IPFS
peer: 'QmV6kA2fB8kTr6jc3pL5zbNsjKbmPUHAPKKHRBYe1kDEyc',
isLoading: 0
}
this.peerInterval = null

this.handleHashChange = this.handleHashChange.bind(this)
this.handleHashSubmit = this.handleHashSubmit.bind(this)
this.handlePeerChange = this.handlePeerChange.bind(this)
this.handlePeerSubmit = this.handlePeerSubmit.bind(this)
}

handleHashChange (event) {
this.setState({
hash: event.target.value
})
}
handlePeerChange (event) {
this.setState({
peer: event.target.value
})
}

handleHashSubmit (event) {
event.preventDefault()
this.setState({
isLoading: this.state.isLoading + 1
})

this.ipfs.files.cat(this.state.hash, (err, data) => {
if (err) console.log('Error', err)

this.setState({
response: data.toString(),
isLoading: this.state.isLoading - 1
})
})
}
handlePeerSubmit (event) {
event.preventDefault()
this.setState({
isLoading: this.state.isLoading + 1
})

this.ipfs.dht.findpeer(this.state.peer, (err, results) => {
if (err) console.log('Error', err)

this.setState({
response: JSON.stringify(results, null, 2),
isLoading: this.state.isLoading - 1
})
})
}

componentDidMount () {
window.ipfs = this.ipfs = new Ipfs({
config: {
Addresses: {
Swarm: []
},
Discovery: {
MDNS: {
Enabled: false
},
webRTCStar: {
Enabled: false
}
},
Bootstrap: [
BootstrapNode
]
},
preload: {
enabled: false
},
libp2p: libp2pBundle
})
this.ipfs.on('ready', () => {
if (this.peerInterval) {
clearInterval(this.peerInterval)
}

this.ipfs.swarm.connect(BootstrapNode, (err) => {
if (err) {
console.log('Error connecting to the node', err)
}
console.log('Connected!')
})

this.peerInterval = setInterval(() => {
this.ipfs.swarm.peers((err, peers) => {
if (err) console.log(err)
if (peers) this.setState({peers: peers.length})
})
}, 2500)
})
}

render () {
return (
<div>
<header className="center">
<h1>Delegated Routing</h1>
<h2>There are currently {this.state.peers} peers.</h2>
</header>
<section className="center">
<form onSubmit={this.handleHashSubmit}>
<label>
Hash:
<input type="text" value={this.state.hash} onChange={this.handleHashChange} />
<input type="submit" value="Find" />
</label>
</form>
<form onSubmit={this.handlePeerSubmit}>
<label>
Peer:
<input type="text" value={this.state.peer} onChange={this.handlePeerChange} />
<input type="submit" value="Find" />
</label>
</form>
</section>
<section className={[this.state.isLoading > 0 ? 'loading' : '', 'loader'].join(' ')}>
<div className="lds-ripple"><div></div><div></div></div>
</section>
<section>
<pre>
{this.state.response}
</pre>
</section>
</div>
)
}
}

module.exports = App
9 changes: 9 additions & 0 deletions examples/delegated-routing/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// eslint-disable-next-line
'use strict'

const React = require('react') // eslint-disable-line no-unused-vars
const ReactDOM = require('react-dom')
const App = require('./App') // eslint-disable-line no-unused-vars
// require('index.css')

ReactDOM.render(<App />, document.getElementById('root'))
Loading