Compare commits

..

102 Commits

Author SHA1 Message Date
8879634363 chore: release version v0.29.1 2020-10-22 14:33:29 +02:00
4a80afce8f chore: update contributors 2020-10-22 14:33:28 +02:00
f75ae341bb test: lock ci on node 14 2020-10-22 14:29:52 +02:00
f2d010a3ab chore: update mplex 2020-10-22 14:29:52 +02:00
e04224a1e2 fix: catch error in upgrader close call 2020-10-22 14:29:52 +02:00
4c6be91588 fix: ensure streams are closed on connection close 2020-10-22 14:29:52 +02:00
5f50054d94 docs: fix typo in transports example readme (#788) 2020-10-22 11:02:00 +02:00
d7d8439e71 docs: update transport example (#770) 2020-10-15 17:28:01 +02:00
4c7a89b710 doc(pubsub): add topicValidators links in API.md table of contents 2020-10-12 12:57:33 +02:00
4eabe07bde chore: update node badge 2020-10-12 12:48:48 +02:00
2fd3b0a0e5 chore: examples not using secio (#747)
* chore: examples not using secio

* chore(docs): remove unused dep

* chore(docs): remove reference of secio in setup

* chore(docs): replace circuit secio reference with noise

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-10-07 16:16:36 +02:00
ec6f7d1cfd chore: lint issue fixed (#775) 2020-10-07 15:39:24 +02:00
a1053bdc54 chore: remove outdated events from libp2p js docs (#766) 2020-10-07 14:51:51 +02:00
0d48fc4f5a test: use ed25519 keys in tests (#669)
* chore: use ed25519 keys in tests

* fix: persisted keybook recheck keybook content for delete

* chore: only store if key not inline

* chore: update peer id

* chore: identify wait for closed streams
2020-10-07 14:50:01 +02:00
60d437f595 fix: flakey identify test firefox (#774) 2020-10-06 15:37:01 +02:00
96df4b7dc4 chore: update aegir and jsdocs for eslint changes (#773) 2020-10-06 14:59:43 +02:00
bb59b518f1 chore: complement 0.29 migration for pubsub subscribe (#755)
* chore: complement 0.29 migration for pubsub subscribe

* chore: update doc/migrations/v0.28-v0.29.md

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-09-16 15:46:05 +02:00
fb4b2734d3 chore: update delegate deps (#753) 2020-09-15 12:47:16 +02:00
0087218194 chore: update getting started connect event (#752) 2020-09-14 13:23:28 +02:00
58b793d700 chore: add libp2p examples repo to release checklist (#746) 2020-09-08 14:47:25 +02:00
63ba2f8fa3 chore: update docs after secio deprecation announcement (#745) 2020-09-08 14:07:06 +02:00
bd26bde876 chore: remove libp2p-pubsub from package table (#743) 2020-08-31 15:19:43 +02:00
3c2a45a9d2 docs: update examples for 0.29 (#742)
* docs: update examples for 0.29

* fix: update examples/libp2p-in-the-browser/package.json

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2020-08-27 18:13:38 +02:00
bbf8ef7065 chore: release version v0.29.0 2020-08-27 16:56:17 +02:00
d6d1a746ea chore: update contributors 2020-08-27 16:56:16 +02:00
28b79a76a8 test: fix pubsub intermittent test (#741) 2020-08-27 16:42:07 +02:00
81e70df742 chore: update interop version (#740) 2020-08-27 15:38:01 +02:00
e9478cee2e chore: release version v0.29.0-rc.1 2020-08-27 15:38:01 +02:00
7be17a3ce1 chore: update contributors 2020-08-27 15:38:01 +02:00
93dda74085 fix: peer record interop with go (#739)
* test: add go peer record interop test

* fix: correct the payload type of peer records

* chore: fix linting

* test: fix envelope test
2020-08-27 15:38:01 +02:00
cfbd52d7f7 chore: migration to 0.29 should use webrtc-star0.20 2020-08-27 15:38:01 +02:00
6cd23ea6c9 chore: use gossipsub0.6 2020-08-27 15:38:01 +02:00
9b75a0f184 chore: bump libp2p-webrtc-star 2020-08-27 15:38:01 +02:00
b606ce0e91 chore: release version v0.29.0-rc.0 2020-08-27 15:38:01 +02:00
64c8c0f097 chore: update contributors 2020-08-27 15:38:01 +02:00
9be582e222 docs: migration 0.28 to 0.29 (#736)
* docs: migration 0.28 to 0.29

* chore: finish pubsub migration

* chore: add uintarray migration and modules

* chore: apply suggestions from code review

Co-authored-by: Jacob Heun <jacobheun@gmail.com>

* chore: add uint8array module reference

* chore: rename files

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
55c9bfac44 feat: gossipsub 1.1 (#733)
* feat: gossipsub 1.1

BREAKING CHANGE: pubsub implementation is now directly exposed and its API was updated according to the new pubsub interface in js-libp2p-interfaces repo

* chore: use gossipsub branch with src added

* fix: add pubsub handlers adapter

* chore: fix deps

* chore: update pubsub docs and examples

* chore: apply suggestions from code review

Co-authored-by: Jacob Heun <jacobheun@gmail.com>

* chore: use new floodsub

* chore: change validator doc set

Co-authored-by: Jacob Heun <jacobheun@gmail.com>

* chore: add new gossipsub src

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
1e869717ff fix: replace node buffers with uint8arrays (#730)
* fix: replace node buffers with uint8arrays

Upgrades all deps and replaces all use of node Buffers with Uint8Arrays

BREAKING CHANGES:

- All deps used by this module now use Uint8Arrays in place of node Buffers

* chore: browser fixes

* chore: remove .only

* chore: stringify uint8array before parsing

* chore: update interop suite

* chore: remove ts from build command

* chore: update deps

* fix: update records to use uint8array

* chore: fix lint

* chore: update deps

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
9107efe121 chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
cd09327eb6 chore: add notice for addressBook.set 2020-08-27 15:38:01 +02:00
ca57e65ecc chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
f574e82a5d chore: apply suggestions from code review 2020-08-27 15:38:01 +02:00
15613ccf19 fix: do not return self on peerstore.peers 2020-08-27 15:38:01 +02:00
dab1c8b2a5 chore: increase bundle size 2020-08-27 15:38:01 +02:00
d437defede chore: rename isEqual to equals in tests 2020-08-27 15:38:01 +02:00
74d414c21f chore: add certified peer records to persisted peer store 2020-08-27 15:38:01 +02:00
8f2e69048f feat: cerified addressbook 2020-08-27 15:38:01 +02:00
b0a36ccbc8 chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
3158366579 fix: revert new identify protocol versions 2020-08-27 15:38:01 +02:00
1d318e12d8 chore: address review 2020-08-27 15:38:01 +02:00
8a97dded26 feat: create self peer record in identify 2020-08-27 15:38:01 +02:00
e50f0eeb7b feat: exchange signed peer records in identify 2020-08-27 15:38:01 +02:00
ee57a643cc chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
89658dd655 chore: address review 2020-08-27 15:38:01 +02:00
4ab125e017 fix: signature compliant with spec 2020-08-27 15:38:01 +02:00
71daac24b1 chore: refactor and better docs 2020-08-27 15:38:01 +02:00
02a5095b9c chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-27 15:38:01 +02:00
3e5d450eca feat: signed peer records record manager 2020-08-27 15:38:01 +02:00
098f3d1dd3 chore: update travis to use node lts and stable
BREAKING CHANGE: this drops testing support in node 10.
2020-08-27 15:38:01 +02:00
689f90a698 revert: reapply "fix: throw if no conn encryption module provided (#665)"
This reapplies commit b621fbdfdc.
2020-08-27 15:38:01 +02:00
0e3cc5866b docs: update peer store api with metadata (#735) 2020-08-21 13:03:29 +02:00
4851680c4d docs: link to official streaming iterables document (#729) 2020-08-06 13:23:18 +02:00
0e18735b8c chore: release version v0.28.10 2020-08-05 19:07:08 +02:00
f68ff35625 chore: update contributors 2020-08-05 19:07:07 +02:00
8c56ec0d23 fix: allow certain keychain operations without a password (#726)
* fix: allow certain keychain operations without a password

Listing, removing, renaming etc keys do not require a password so
the user should not be required to provide one.

This means we don't have to prompt the user to create a password
when they aren't going to do any operations that require a password.

* fix: make keychain pass optional

* fix: support libp2p creation without keychain pass

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-05 19:03:11 +02:00
fa5ee873e3 chore: update node and npm badges (#727) 2020-08-05 18:26:50 +02:00
51d7ca44c1 feat(keychain): add support for ed25519 and secp keys (#725)
* feat(keychain): add support for ed25519 and secp keys

* chore: bump crypto

* refactor: cleanup keychain usage
2020-08-05 18:19:10 +02:00
726a746479 fix(identify): make agentversion dynamic and add it to the peerstore (#724) 2020-08-04 18:39:05 +02:00
a331b84f13 docs(example): add pubssub message filter example
moved the filter folder

rename folder

docs(refactor): clean up pubsub validator example
2020-07-31 12:09:34 +02:00
78d152dd68 chore: release version v0.28.9 2020-07-27 11:58:53 +02:00
7e14aa19b5 chore: update contributors 2020-07-27 11:58:52 +02:00
2440c872df fix: ping multiaddr from peer not previously stored in peerstore (#719) 2020-07-27 10:53:23 +02:00
6c7e5e5eef chore: release version v0.28.8 2020-07-20 17:23:01 +02:00
388df6b6e6 chore: update contributors 2020-07-20 17:23:00 +02:00
7dbfe6ab1a chore: update libp2p-crypto (#717)
This includes a patch for ed25519 interop with Go
2020-07-20 17:18:09 +02:00
cea59a1fe4 docs: update addressBook get and getMultiaddrsForPeer return values (#716) 2020-07-20 16:17:39 +02:00
7da9ad44ab fix: create dial target for peer with no known addrs (#715) 2020-07-20 14:54:03 +02:00
3896941128 docs: use tag for webrtc-star discovery config (#713) 2020-07-20 14:51:24 +02:00
856b38de67 chore: add migration guide template (#711) 2020-07-16 18:14:02 +02:00
798d7b73c1 chore: release version v0.28.7 2020-07-14 19:13:55 +02:00
f2d0d8b51d chore: update contributors 2020-07-14 19:13:54 +02:00
999c1b7740 fix: retimer reschedule does not work as interval (#710)
* fix: retimer reschedule does not work as interval

* chore: apply suggestions from code review

Co-authored-by: Jacob Heun <jacobheun@gmail.com>

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-07-14 19:11:29 +02:00
99ff361a55 chore: release version v0.28.6 2020-07-14 16:13:00 +02:00
6115f8b680 chore: update contributors 2020-07-14 16:12:59 +02:00
9ccab40fc8 fix: not dial all known peers in parallel on startup (#698)
* fix: not dial all known peers on startup

* feat: connection manager should proactively connect to peers from peerStore

* chore: increase bundle size

* fix: do connMgr proactive dial on an interval

* chore: address review

* chore: use retimer reschedule

* chore: address review

* fix: use minConnections in default config

* chore: minPeers to minConnections everywhere
2020-07-14 16:05:26 +02:00
619e5dd73c chore: release version v0.28.5 2020-07-10 17:26:21 +02:00
0fd23f6a5f chore: update contributors 2020-07-10 17:26:21 +02:00
5a84dd56d0 fix: pass libp2p to the dht (#700) 2020-07-10 17:16:45 +02:00
7b05d6922b docs: add dht discovery documentation reference (#697)
Extended the description with a link for how to configure the libp2p-kad-dht for peer discovery.
2020-07-08 15:03:56 +02:00
08a4fad80b chore: add modules to update in 0.28.x migration (#695) 2020-07-08 14:12:40 +02:00
51da8874d8 test: add pubsub reconnect test (#693)
* test: add pubsub reconnect test

* chore: dep bump

* chore: remove temp pubsub dep

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>

Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
2020-07-07 18:31:51 +02:00
5cd8c19567 docs(fix): update link to ipfs config files (#690) 2020-07-07 12:56:17 +02:00
db766eaca9 chore: update circuit readme (#689)
* chore: update circuit readme

* chore: address review
2020-07-07 12:55:52 +02:00
a1308d640d chore: release version v0.28.4 2020-07-03 16:14:47 +02:00
19e7254c3d chore: update contributors 2020-07-03 16:14:47 +02:00
f4898eb883 chore: add test for pubsub on reconnect (#691)
* chore: add test for pubsub on reconnect

* chore: update interface dep

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-07-03 16:08:37 +02:00
9bec719fce docs(fix): link to streaming-iterables' repo (#688) 2020-07-02 13:34:01 +02:00
b664c0aafd chore: libp2p in the browser using webrtc servers (#684) 2020-06-29 16:31:07 +02:00
b524bbf627 chore: release version v0.28.3 2020-06-18 15:40:38 +02:00
07bd269fb0 chore: update contributors 2020-06-18 15:40:38 +02:00
a8219e61a0 fix: catch pipe errors (#678)
* fix: catch pipe errors

There were some pipe errors not being caught. This can result in unhandled exceptions being thrown

* fix: catch pipe errors in identify push handler
2020-06-18 15:33:08 +02:00
a1590acc8b chore: add libp2p docs website to release checklist 2020-06-18 11:48:31 +02:00
136 changed files with 4314 additions and 1133 deletions

View File

@ -45,9 +45,15 @@ const after = async () => {
}
module.exports = {
bundlesize: { maxSize: '200kB' },
bundlesize: { maxSize: '225kB' },
hooks: {
pre: before,
post: after
},
webpack: {
node: {
// needed by bcrypto
Buffer: true
}
}
}

View File

@ -6,8 +6,8 @@ stages:
- cov
node_js:
- '10'
- '12'
- 'lts/*'
- '14'
os:
- linux

View File

@ -1,3 +1,269 @@
<a name="0.29.1"></a>
## [0.29.1](https://github.com/libp2p/js-libp2p/compare/v0.29.0...v0.29.1) (2020-10-22)
### Bug Fixes
* catch error in upgrader close call ([e04224a](https://github.com/libp2p/js-libp2p/commit/e04224a))
* ensure streams are closed on connection close ([4c6be91](https://github.com/libp2p/js-libp2p/commit/4c6be91))
* flakey identify test firefox ([#774](https://github.com/libp2p/js-libp2p/issues/774)) ([60d437f](https://github.com/libp2p/js-libp2p/commit/60d437f))
<a name="0.29.0"></a>
# [0.29.0](https://github.com/libp2p/js-libp2p/compare/v0.28.10...v0.29.0) (2020-08-27)
### Bug Fixes
* do not return self on peerstore.peers ([15613cc](https://github.com/libp2p/js-libp2p/commit/15613cc))
* peer record interop with go ([#739](https://github.com/libp2p/js-libp2p/issues/739)) ([93dda74](https://github.com/libp2p/js-libp2p/commit/93dda74))
* replace node buffers with uint8arrays ([#730](https://github.com/libp2p/js-libp2p/issues/730)) ([1e86971](https://github.com/libp2p/js-libp2p/commit/1e86971))
* revert new identify protocol versions ([3158366](https://github.com/libp2p/js-libp2p/commit/3158366))
* signature compliant with spec ([4ab125e](https://github.com/libp2p/js-libp2p/commit/4ab125e))
### Chores
* update travis to use node lts and stable ([098f3d1](https://github.com/libp2p/js-libp2p/commit/098f3d1))
### Features
* cerified addressbook ([8f2e690](https://github.com/libp2p/js-libp2p/commit/8f2e690))
* create self peer record in identify ([8a97dde](https://github.com/libp2p/js-libp2p/commit/8a97dde))
* exchange signed peer records in identify ([e50f0ee](https://github.com/libp2p/js-libp2p/commit/e50f0ee))
* gossipsub 1.1 ([#733](https://github.com/libp2p/js-libp2p/issues/733)) ([55c9bfa](https://github.com/libp2p/js-libp2p/commit/55c9bfa))
* signed peer records record manager ([3e5d450](https://github.com/libp2p/js-libp2p/commit/3e5d450))
### Reverts
* reapply "fix: throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665))" ([689f90a](https://github.com/libp2p/js-libp2p/commit/689f90a))
### BREAKING CHANGES
* pubsub implementation is now directly exposed and its API was updated according to the new pubsub interface in js-libp2p-interfaces repo
* chore: use gossipsub branch with src added
* fix: add pubsub handlers adapter
* chore: fix deps
* chore: update pubsub docs and examples
* chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* chore: use new floodsub
* chore: change validator doc set
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* chore: add new gossipsub src
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* - All deps used by this module now use Uint8Arrays in place of node Buffers
* chore: browser fixes
* chore: remove .only
* chore: stringify uint8array before parsing
* chore: update interop suite
* chore: remove ts from build command
* chore: update deps
* fix: update records to use uint8array
* chore: fix lint
* chore: update deps
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* this drops testing support in node 10.
<a name="0.29.0-rc.1"></a>
# [0.29.0-rc.1](https://github.com/libp2p/js-libp2p/compare/v0.29.0-rc.0...v0.29.0-rc.1) (2020-08-27)
### Bug Fixes
* peer record interop with go ([#739](https://github.com/libp2p/js-libp2p/issues/739)) ([c4c7ef9](https://github.com/libp2p/js-libp2p/commit/c4c7ef9))
<a name="0.29.0-rc.0"></a>
# [0.29.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.28.10...v0.29.0-rc.0) (2020-08-25)
### Bug Fixes
* do not return self on peerstore.peers ([e1b8edc](https://github.com/libp2p/js-libp2p/commit/e1b8edc))
* replace node buffers with uint8arrays ([#730](https://github.com/libp2p/js-libp2p/issues/730)) ([507f8c4](https://github.com/libp2p/js-libp2p/commit/507f8c4))
* revert new identify protocol versions ([a798c65](https://github.com/libp2p/js-libp2p/commit/a798c65))
* signature compliant with spec ([97b5d2a](https://github.com/libp2p/js-libp2p/commit/97b5d2a))
### Chores
* update travis to use node lts and stable ([c272288](https://github.com/libp2p/js-libp2p/commit/c272288))
### Features
* cerified addressbook ([e0ed258](https://github.com/libp2p/js-libp2p/commit/e0ed258))
* create self peer record in identify ([83922a7](https://github.com/libp2p/js-libp2p/commit/83922a7))
* exchange signed peer records in identify ([f835457](https://github.com/libp2p/js-libp2p/commit/f835457))
* gossipsub 1.1 ([#733](https://github.com/libp2p/js-libp2p/issues/733)) ([e14ce40](https://github.com/libp2p/js-libp2p/commit/e14ce40))
* signed peer records record manager ([f95edf1](https://github.com/libp2p/js-libp2p/commit/f95edf1))
### Reverts
* reapply "fix: throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665))" ([ad7f02e](https://github.com/libp2p/js-libp2p/commit/ad7f02e))
### BREAKING CHANGES
* pubsub implementation is now directly exposed and its API was updated according to the new pubsub interface in js-libp2p-interfaces repo
* chore: use gossipsub branch with src added
* fix: add pubsub handlers adapter
* chore: fix deps
* chore: update pubsub docs and examples
* chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* chore: use new floodsub
* chore: change validator doc set
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* chore: add new gossipsub src
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* - All deps used by this module now use Uint8Arrays in place of node Buffers
* chore: browser fixes
* chore: remove .only
* chore: stringify uint8array before parsing
* chore: update interop suite
* chore: remove ts from build command
* chore: update deps
* fix: update records to use uint8array
* chore: fix lint
* chore: update deps
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* this drops testing support in node 10.
<a name="0.28.10"></a>
## [0.28.10](https://github.com/libp2p/js-libp2p/compare/v0.28.9...v0.28.10) (2020-08-05)
### Bug Fixes
* allow certain keychain operations without a password ([#726](https://github.com/libp2p/js-libp2p/issues/726)) ([8c56ec0](https://github.com/libp2p/js-libp2p/commit/8c56ec0))
* **identify:** make agentversion dynamic and add it to the peerstore ([#724](https://github.com/libp2p/js-libp2p/issues/724)) ([726a746](https://github.com/libp2p/js-libp2p/commit/726a746))
### Features
* **keychain:** add support for ed25519 and secp keys ([#725](https://github.com/libp2p/js-libp2p/issues/725)) ([51d7ca4](https://github.com/libp2p/js-libp2p/commit/51d7ca4))
<a name="0.28.9"></a>
## [0.28.9](https://github.com/libp2p/js-libp2p/compare/v0.28.8...v0.28.9) (2020-07-27)
### Bug Fixes
* ping multiaddr from peer not previously stored in peerstore ([#719](https://github.com/libp2p/js-libp2p/issues/719)) ([2440c87](https://github.com/libp2p/js-libp2p/commit/2440c87))
<a name="0.28.8"></a>
## [0.28.8](https://github.com/libp2p/js-libp2p/compare/v0.28.7...v0.28.8) (2020-07-20)
### Bug Fixes
* create dial target for peer with no known addrs ([#715](https://github.com/libp2p/js-libp2p/issues/715)) ([7da9ad4](https://github.com/libp2p/js-libp2p/commit/7da9ad4))
<a name="0.28.7"></a>
## [0.28.7](https://github.com/libp2p/js-libp2p/compare/v0.28.6...v0.28.7) (2020-07-14)
### Bug Fixes
* retimer reschedule does not work as interval ([#710](https://github.com/libp2p/js-libp2p/issues/710)) ([999c1b7](https://github.com/libp2p/js-libp2p/commit/999c1b7))
<a name="0.28.6"></a>
## [0.28.6](https://github.com/libp2p/js-libp2p/compare/v0.28.5...v0.28.6) (2020-07-14)
### Bug Fixes
* not dial all known peers in parallel on startup ([#698](https://github.com/libp2p/js-libp2p/issues/698)) ([9ccab40](https://github.com/libp2p/js-libp2p/commit/9ccab40))
<a name="0.28.5"></a>
## [0.28.5](https://github.com/libp2p/js-libp2p/compare/v0.28.4...v0.28.5) (2020-07-10)
### Bug Fixes
* pass libp2p to the dht ([#700](https://github.com/libp2p/js-libp2p/issues/700)) ([5a84dd5](https://github.com/libp2p/js-libp2p/commit/5a84dd5))
<a name="0.28.4"></a>
## [0.28.4](https://github.com/libp2p/js-libp2p/compare/v0.28.3...v0.28.4) (2020-07-03)
<a name="0.28.3"></a>
## [0.28.3](https://github.com/libp2p/js-libp2p/compare/v0.28.2...v0.28.3) (2020-06-18)
### Bug Fixes
* catch pipe errors ([#678](https://github.com/libp2p/js-libp2p/issues/678)) ([a8219e6](https://github.com/libp2p/js-libp2p/commit/a8219e6))
<a name="0.28.2"></a>
## [0.28.2](https://github.com/libp2p/js-libp2p/compare/v0.28.1...v0.28.2) (2020-06-15)

45
MIGRATION_TEMPLATE.md Normal file
View File

@ -0,0 +1,45 @@
<!--Specify versions for migration below-->
# Migrating to libp2p@__
A migration guide for refactoring your application code from libp2p v__ to v__.
## Table of Contents
- [API](#api)
- [Module Updates](#module-updates)
## API
<!--Describe breaking APIs with examples for Before and After
Example:
### Peer Discovery
__Describe__
**Before**
```js
```
**After**
```js
```
-->
## Module Updates
With this release you should update the following libp2p modules if you are relying on them:
<!--Specify module versions in JSON for migration below.
It's recommended to check package.json changes for this:
`git diff <release> <prev> -- package.json`
-->
```json
```

View File

@ -23,8 +23,8 @@
<a href="https://david-dm.org/libp2p/js-libp2p"><img src="https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square" /></a>
<a href="https://github.com/feross/standard"><img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square"></a>
<a href="https://github.com/RichardLitt/standard-readme"><img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D12.0.0-orange.svg?style=flat-square" /></a>
<br>
</p>
@ -168,7 +168,6 @@ List of packages currently in existence for libp2p
| **data types** |
| [`peer-id`](//github.com/libp2p/js-peer-id) | [![npm](https://img.shields.io/npm/v/peer-id.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-id/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-id.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-id) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-peer-id/master)](https://travis-ci.com/libp2p/js-peer-id) | [![codecov](https://codecov.io/gh/libp2p/js-peer-id/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-peer-id) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| **pubsub** |
| [`libp2p-pubsub`](//github.com/libp2p/js-libp2p-pubsub) | [![npm](https://img.shields.io/npm/v/libp2p-pubsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-pubsub/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-pubsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-pubsub) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-pubsub/master)](https://travis-ci.com/libp2p/js-libp2p-pubsub) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-pubsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-pubsub) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |
| [`libp2p-floodsub`](//github.com/libp2p/js-libp2p-floodsub) | [![npm](https://img.shields.io/npm/v/libp2p-floodsub.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-floodsub/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-floodsub.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-floodsub) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-floodsub/master)](https://travis-ci.com/libp2p/js-libp2p-floodsub) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-floodsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-floodsub) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-gossipsub`](//github.com/ChainSafe/js-libp2p-gossipsub) | [![npm](https://img.shields.io/npm/v/libp2p-gossipsub.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/js-libp2p-gossipsub/releases) | [![Deps](https://david-dm.org/ChainSafe/js-libp2p-gossipsub.svg?style=flat-square)](https://david-dm.org/ChainSafe/js-libp2p-gossipsub) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/js-libp2p-gossipsub/master)](https://travis-ci.com/ChainSafe/js-libp2p-gossipsub) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-gossipsub/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-gossipsub) | [Cayman Nava](mailto:caymannava@gmail.com) |
| **extensions** |

View File

@ -26,6 +26,8 @@
- Documentation
- [ ] Ensure that README.md is up to date
- [ ] Ensure that all the examples run
- [ ] Ensure [libp2p/js-libp2p-examples](https://github.com/libp2p/js-libp2p-examples) is updated
- [ ] Ensure that [libp2p/docs](https://github.com/libp2p/docs) is updated
- Communication
- [ ] Create the release issue
- [ ] Take a snapshot between of everyone that has contributed to this release (including its subdeps in IPFS, libp2p, IPLD and multiformats) using [`name-your-contributors`](https://www.npmjs.com/package/name-your-contributors). Generate a nice markdown list with [this script](https://gist.github.com/jacobheun/d2ff479ca991733c13cdcf688a1317e5)

View File

@ -2,7 +2,7 @@
* [Static Functions](#static-functions)
* [`create`](#create)
* [Instance Methods](#instance-methods)
* [Instance Methods](#libp2p-instance-methods)
* [`start`](#start)
* [`stop`](#stop)
* [`dial`](#dial)
@ -46,6 +46,10 @@
* [`pubsub.publish`](#pubsubpublish)
* [`pubsub.subscribe`](#pubsubsubscribe)
* [`pubsub.unsubscribe`](#pubsubunsubscribe)
* [`pubsub.on`](#pubsubon)
* [`pubsub.removeListener`](#pubsubremovelistener)
* [`pubsub.topicValidators.set`](#pubsubtopicvalidatorsset)
* [`pubsub.topicValidators.delete`](#pubsubtopicvalidatorsdelete)
* [`connectionManager.get`](#connectionmanagerget)
* [`connectionManager.setPeerValue`](#connectionmanagersetpeervalue)
* [`connectionManager.size`](#connectionmanagersize)
@ -85,17 +89,17 @@ Creates an instance of Libp2p.
| Name | Type | Description |
|------|------|-------------|
| options | `object` | libp2p options |
| options.modules | [`Array<object>`](./CONFIGURATION.md#modules) | libp2p modules to use |
| options.modules | [`Array<object>`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use |
| [options.addresses] | `{ listen: Array<string>, announce: Array<string>, noAnnounce: Array<string> }` | Addresses for transport listening and to advertise to the network |
| [options.config] | `object` | libp2p modules configuration and core configuration |
| [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager configuration |
| [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager configuration |
| [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) |
| [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager [configuration](./CONFIGURATION.md#configuring-transport-manager) |
| [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) |
| [options.dialer] | [`object`](./CONFIGURATION.md#configuring-dialing) | libp2p Dialer configuration
| [options.keychain] | [`object`](./CONFIGURATION.md#setup-with-keychain) | keychain configuration |
| [options.metrics] | [`object`](./CONFIGURATION.md#configuring-metrics) | libp2p Metrics configuration
| [options.dialer] | [`object`](./CONFIGURATION.md#configuring-dialing) | libp2p Dialer [configuration](./CONFIGURATION.md#configuring-dialing)
| [options.keychain] | [`object`](./CONFIGURATION.md#setup-with-keychain) | keychain [configuration](./CONFIGURATION.md#setup-with-keychain) |
| [options.metrics] | [`object`](./CONFIGURATION.md#configuring-metrics) | libp2p Metrics [configuration](./CONFIGURATION.md#configuring-metrics) |
| [options.peerId] | [`PeerId`][peer-id] | peerId instance (it will be created if not provided) |
| [options.peerStore] | [`object`](./CONFIGURATION.md#configuring-peerstore) | libp2p PeerStore configuration |
| [options.peerStore] | [`object`](./CONFIGURATION.md#configuring-peerstore) | libp2p PeerStore [configuration](./CONFIGURATION.md#configuring-peerstore) |
For Libp2p configurations and modules details read the [Configuration Document](./CONFIGURATION.md).
@ -306,7 +310,7 @@ Dials to another peer in the network and selects a protocol to communicate with
| Type | Description |
|------|-------------|
| `Promise<{ stream:*, protocol:string }>` | Promise resolves with a [duplex stream](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it) and the protocol used |
| `Promise<{ stream:*, protocol:string }>` | Promise resolves with a [duplex stream](https://github.com/libp2p/js-libp2p/blob/master/doc/STREAMING_ITERABLES.md#duplex) and the protocol used |
#### Example
@ -418,7 +422,7 @@ const latency = await libp2p.ping(otherPeerId)
## multiaddrs
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs
provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses.
@ -585,7 +589,7 @@ Writes a value to a key in the DHT.
| Name | Type | Description |
|------|------|-------------|
| key | `string` | key to add to the dht |
| value | `Buffer` | value to add to the dht |
| value | `Uint8Array` | value to add to the dht |
| [options] | `object` | put options |
| [options.minPeers] | `number` | minimum number of peers required to successfully put (default: closestPeers.length) |
@ -600,7 +604,7 @@ Writes a value to a key in the DHT.
```js
// ...
const key = '/key'
const value = Buffer.from('oh hello there')
const value = uint8ArrayFromString('oh hello there')
await libp2p.contentRouting.put(key, value)
```
@ -623,7 +627,7 @@ Queries the DHT for a value stored for a given key.
| Type | Description |
|------|-------------|
| `Promise<Buffer>` | Value obtained from the DHT |
| `Promise<Uint8Array>` | Value obtained from the DHT |
#### Example
@ -653,7 +657,7 @@ Queries the DHT for the n values stored for the given key (without sorting).
| Type | Description |
|------|-------------|
| `Promise<Array<{from: PeerId, val: Buffer}>>` | Array of records obtained from the DHT |
| `Promise<Array<{from: PeerId, val: Uint8Array}>>` | Array of records obtained from the DHT |
#### Example
@ -760,7 +764,7 @@ Get the known [`Addresses`][address] of a provided peer.
| Type | Description |
|------|-------------|
| `Array<Address>` | Array of peer's [`Addresses`][address] containing the multiaddr and its metadata |
| `Array<Address>|undefined` | Array of peer's [`Addresses`][address] containing the multiaddr and its metadata if available, otherwise undefined |
#### Example
@ -797,7 +801,7 @@ Get the known `Multiaddr` of a provided peer. All returned multiaddrs will inclu
| Type | Description |
|------|-------------|
| `Array<Multiaddr>` | Array of peer's multiaddr |
| `Array<Multiaddr>|undefined` | Array of peer's multiaddr if available, otherwise undefined |
#### Example
@ -814,7 +818,9 @@ peerStore.addressBook.getMultiaddrsForPeer(peerId)
### peerStore.addressBook.set
Set known `multiaddrs` of a given peer.
Set known `multiaddrs` of a given peer. This will replace previously stored multiaddrs, if available.
Replacing stored multiaddrs might result in losing obtained certified addresses, which is not desirable.
Consider using `addressBook.add()` if you're not sure this is what you want to do.
`peerStore.addressBook.set(peerId, multiaddrs)`
@ -968,7 +974,7 @@ Delete the provided peer from the book.
```js
peerStore.metadataBook.delete(peerId)
// false
peerStore.metadataBook.set(peerId, 'nickname', Buffer.from('homePeer'))
peerStore.metadataBook.set(peerId, 'nickname', uint8ArrayFromString('homePeer'))
peerStore.metadataBook.delete(peerId)
// true
```
@ -997,7 +1003,7 @@ Deletes the provided peer metadata key-value pair from the book.
```js
peerStore.metadataBook.deleteValue(peerId, 'location')
// false
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin'))
peerStore.metadataBook.deleteValue(peerId, 'location')
// true
```
@ -1018,14 +1024,14 @@ Get the known metadata of a provided peer.
| Type | Description |
|------|-------------|
| `Map<string, Buffer>` | Peer Metadata |
| `Map<string, Uint8Array>` | Peer Metadata |
#### Example
```js
peerStore.metadataBook.get(peerId)
// undefined
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin'))
peerStore.metadataBook.get(peerId)
// Metadata Map
```
@ -1047,14 +1053,14 @@ Get specific metadata of a provided peer.
| Type | Description |
|------|-------------|
| `Map<string, Buffer>` | Peer Metadata |
| `Map<string, Uint8Array>` | Peer Metadata |
#### Example
```js
peerStore.metadataBook.getValue(peerId, 'location')
// undefined
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin'))
peerStore.metadataBook.getValue(peerId, 'location')
// Metadata Map
```
@ -1071,7 +1077,7 @@ Set known metadata of a given `peerId`.
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| key | `string` | key of the metadata value to store |
| value | `Buffer` | metadata value to store |
| value | `Uint8Array` | metadata value to store |
#### Returns
@ -1082,7 +1088,7 @@ Set known metadata of a given `peerId`.
#### Example
```js
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin'))
```
### peerStore.protoBook.delete
@ -1216,7 +1222,7 @@ Get the stored information of a given peer, namely its [`PeerId`][peer-id], know
| Type | Description |
|------|-------------|
| `{ id: PeerId, addresses: Array<Address>, protocols: Array<string> }` | Peer information of the provided peer |
| `{ id: PeerId, addresses: Array<Address>, metadata: Map<string, Buffer>}, protocols: Array<string> }` | Peer information of the provided peer |
#### Example
@ -1243,13 +1249,13 @@ Get all the stored information of every peer.
| Type | Description |
|------|-------------|
| `Map<string, { id: PeerId, addresses: Array<Address>, protocols: Array<string> }>` | Peer data of every peer known |
| `Map<string, { id: PeerId, addresses: Array<Address>, metadata: Map<string, Buffer>}, protocols: Array<string> }>` | Peer data of every peer known |
#### Example
```js
for (let [peerIdString, peer] of peerStore.peers.entries()) {
// peer { id, addresses, protocols }
// peer { id, addresses, metadata, protocols }
}
```
@ -1306,7 +1312,7 @@ Publishes messages to the given topics.
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to publish |
| data | `Buffer` | data to publish |
| data | `Uint8Array` | data to publish |
#### Returns
@ -1318,23 +1324,22 @@ Publishes messages to the given topics.
```js
const topic = 'topic'
const data = Buffer.from('data')
const data = uint8ArrayFromString('data')
await libp2p.pubsub.publish(topic, data)
```
### pubsub.subscribe
Subscribes the given handler to a pubsub topic.
Subscribes to a pubsub topic.
`libp2p.pubsub.subscribe(topic, handler)`
`libp2p.pubsub.subscribe(topic)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to subscribe |
| handler | `function({ from: string, data: Buffer, seqno: Buffer, topicIDs: Array<string>, signature: Buffer, key: Buffer })` | handler for new data on topic |
#### Returns
@ -1350,21 +1355,21 @@ const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.subscribe(topic, handler)
libp2p.pubsub.on(topic, handler)
libp2p.pubsub.subscribe(topic)
```
### pubsub.unsubscribe
Unsubscribes the given handler from a pubsub topic. If no handler is provided, all handlers for the topic are removed.
Unsubscribes from a pubsub topic.
`libp2p.pubsub.unsubscribe(topic, handler)`
`libp2p.pubsub.unsubscribe(topic)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to unsubscribe |
| handler | `function(<object>)` | handler subscribed |
#### Returns
@ -1380,7 +1385,129 @@ const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.unsubscribe(topic, handler)
libp2p.pubsub.removeListener(topic handler)
libp2p.pubsub.unsubscribe(topic)
```
## pubsub.on
A Pubsub router is an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) and uses its events for pubsub message handlers.
`libp2p.pubsub.on(topic, handler)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to listen |
| handler | `function({ from: string, data: Uint8Array, seqno: Uint8Array, topicIDs: Array<string>, signature: Uint8Array, key: Uint8Array })` | handler for new data on topic |
#### Returns
| Type | Description |
|------|-------------|
| `void` | |
#### Example
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.on(topic, handler)
libp2p.pubsub.subscribe(topic)
```
## pubsub.removeListener
A Pubsub router is an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) and uses its events for pubsub message handlers.
`libp2p.pubsub.removeListener(topic, handler)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to remove listener |
| handler | `function({ from: string, data: Uint8Array, seqno: Uint8Array, topicIDs: Array<string>, signature: Uint8Array, key: Uint8Array })` | handler for new data on topic |
#### Returns
| Type | Description |
|------|-------------|
| `void` | |
#### Example
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.removeListener(topic handler)
libp2p.pubsub.unsubscribe(topic)
```
## pubsub.topicValidators.set
Pubsub routers support message validators per topic, which will validate the message before its propagations. Set is used to specify a validator for a topic.
`libp2p.pubsub.topicValidators.set(topic, validator)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to bind a validator |
| handler | `function({ topic: string, msg: RPC })` | validator for new data on topic |
#### Returns
| Type | Description |
|------|-------------|
| `Map<string, function(string, RPC)>` | The `Map` object |
#### Example
```js
const topic = 'topic'
const validateMessage = (msgTopic, msg) => {
const input = uint8ArrayToString(msg.data)
const validInputs = ['a', 'b', 'c']
if (!validInputs.includes(input)) {
throw new Error('no valid input received')
}
}
libp2p.pubsub.topicValidators.set(topic, validateMessage)
```
## pubsub.topicValidators.delete
Pubsub routers support message validators per topic, which will validate the message before its propagations. Delete is used to remove a validator for a topic.
`libp2p.pubsub.topicValidators.delete(topic)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| topic | `string` | topic to remove a validator |
#### Returns
| Type | Description |
|------|-------------|
| `boolean` | `true` if an element in the Map object existed and has been removed, or `false` if the element does not exist. |
#### Example
```js
const topic = 'topic'
libp2p.pubsub.topicValidators.delete(topic)
```
### connectionManager.get
@ -1454,7 +1581,7 @@ Create a key in the keychain.
|------|------|-------------|
| name | `string` | The local key name. It cannot already exist. |
| type | `string` | One of the key types; 'rsa' |
| size | `number` | The key size in bits. |
| [size] | `number` | The key size in bits. Must be provided for rsa keys. |
#### Returns
@ -1677,19 +1804,19 @@ Encrypt protected data using the Cryptographic Message Syntax (CMS).
| Name | Type | Description |
|------|------|-------------|
| name | `string` | The local key name. |
| data | `Buffer` | The data to encrypt. |
| data | `Uint8Array` | The data to encrypt. |
#### Returns
| Type | Description |
|------|-------------|
| `Promise<Buffer>` | Encrypted data as a PKCS #7 message in DER. |
| `Promise<Uint8Array>` | Encrypted data as a PKCS #7 message in DER. |
#### Example
```js
const keyInfo = await libp2p.keychain.createKey('keyTest', 'rsa', 4096)
const enc = await libp2p.keychain.cms.encrypt('keyTest', Buffer.from('data'))
const enc = await libp2p.keychain.cms.encrypt('keyTest', uint8ArrayFromString('data'))
```
### keychain.cms.decrypt
@ -1709,13 +1836,13 @@ The keychain must contain one of the keys used to encrypt the data. If none of
| Type | Description |
|------|-------------|
| `Promise<Buffer>` | Decrypted data. |
| `Promise<Uint8Array>` | Decrypted data. |
#### Example
```js
const keyInfo = await libp2p.keychain.createKey('keyTest', 'rsa', 4096)
const enc = await libp2p.keychain.cms.encrypt('keyTest', Buffer.from('data'))
const enc = await libp2p.keychain.cms.encrypt('keyTest', uint8ArrayFromString('data'))
const decData = await libp2p.keychain.cms.decrypt(enc)
```
@ -1801,7 +1928,7 @@ console.log(peerStats.toJSON())
## Events
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
### libp2p

View File

@ -52,7 +52,7 @@ The libp2p ecosystem contains at least one module for each of these subsystems.
After selecting the modules to use, it is also possible to configure each one according to your needs.
Bear in mind that only a **transport** and **connection encryption** are required, while all the other subsystems are optional.
Bear in mind that a **transport** and **connection encryption** module are **required**, while all the other subsystems are optional.
### Transport
@ -98,7 +98,7 @@ If you want to know more about libp2p stream multiplexing, you should read the f
Some available connection encryption protocols:
- [NodeFactoryIo/js-libp2p-noise](https://github.com/NodeFactoryIo/js-libp2p-noise)
- [libp2p/js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)
- [libp2p/js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio) ⚠️ [DEPRECATED](https://blog.ipfs.io/2020-08-07-deprecating-secio)
If none of the available connection encryption mechanisms fulfills your needs, you can create a libp2p compatible one. A libp2p connection encryption protocol just needs to be compliant with the [Crypto Interface](https://github.com/libp2p/js-interfaces/tree/master/src/crypto).
@ -223,7 +223,7 @@ Besides the `modules` and `config`, libp2p allows other internal options and con
// Creating a libp2p node with:
// transport: websockets + tcp
// stream-muxing: mplex
// crypto-channel: secio
// crypto-channel: noise
// discovery: multicast-dns
// dht: kad-dht
// pubsub: gossipsub
@ -232,7 +232,7 @@ const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns')
const DHT = require('libp2p-kad-dht')
const GossipSub = require('libp2p-gossipsub')
@ -244,7 +244,7 @@ const node = await Libp2p.create({
new WS() // It can take instances too!
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS],
dht: DHT,
pubsub: GossipSub
@ -258,19 +258,19 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS]
},
config: {
peerDiscovery: {
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minConnections)
// The `tag` property will be searched when creating the instance of your Peer Discovery service.
// The associated object, will be passed to the service when it is instantiated.
[MulticastDNS.tag]: {
@ -291,7 +291,7 @@ const Libp2p = require('libp2p')
const WS = require('libp2p-websockets')
const WebRTCStar = require('libp2p-webrtc-star')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
@ -300,11 +300,11 @@ const node = await Libp2p.create({
WebRTCStar
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
},
config: {
peerDiscovery: {
webRTCStar: {
[WebRTCStar.tag]: {
enabled: true
}
}
@ -318,14 +318,14 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const GossipSub = require('libp2p-gossipsub')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
pubsub: GossipSub
},
config: {
@ -345,14 +345,14 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const DHT = require('libp2p-kad-dht')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
dht: DHT
},
config: {
@ -375,7 +375,7 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const DelegatedPeerRouter = require('libp2p-delegated-peer-routing')
const DelegatedContentRouter = require('libp2p-delegated-content-routing')
const PeerId = require('peer-id')
@ -387,7 +387,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
contentRouting: [
new DelegatedContentRouter(peerId)
],
@ -405,13 +405,13 @@ const node = await Libp2p.create({
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
@ -438,14 +438,14 @@ Libp2p allows you to setup a secure keychain to manage your keys. The keychain c
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const LevelStore = require('datastore-level')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
keychain: {
pass: 'notsafepassword123456789',
@ -472,13 +472,13 @@ The below configuration example shows how the dialer should be configured, with
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
dialer: {
maxParallelDials: 100,
@ -495,13 +495,13 @@ The Connection Manager prunes Connections in libp2p whenever certain limits are
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
connectionManager: {
maxConnections: Infinity,
@ -526,7 +526,7 @@ The Transport Manager is responsible for managing the libp2p transports life cyc
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const { FaultTolerance } = require('libp2p/src/transport-manager')}
@ -534,7 +534,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
transportManager: {
faultTolerance: FaultTolerance.NO_FATAL
@ -560,13 +560,13 @@ The below configuration example shows how the metrics should be configured. Asid
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
metrics: {
enabled: true,
@ -599,7 +599,7 @@ The below configuration example shows how the PeerStore should be configured. As
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const LevelStore = require('datastore-level')
@ -607,7 +607,7 @@ const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
datastore: new LevelStore('path/to/store'),
peerStore: {
@ -625,7 +625,7 @@ Some Transports can be passed additional options when they are created. For exam
const Libp2p = require('libp2p')
const WebRTCStar = require('libp2p-webrtc-star')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const wrtc = require('wrtc')
const transportKey = WebRTCStar.prototype[Symbol.toStringTag]
@ -633,7 +633,7 @@ const node = await Libp2p.create({
modules: {
transport: [WebRTCStar],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
config: {
transport: {
@ -649,8 +649,8 @@ const node = await Libp2p.create({
As libp2p is designed to be a modular networking library, its usage will vary based on individual project needs. We've included links to some existing project configurations for your reference, in case you wish to replicate their configuration:
- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-nodejs.js) - libp2p configuration used by js-ipfs when running in Node.js
- [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/tree/master/src/core/runtime/libp2p-browser.js) - libp2p configuration used by js-ipfs when running in a Browser (that supports WebRTC)
- [libp2p-ipfs-nodejs](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs/src/core/runtime/libp2p-nodejs.js) - libp2p configuration used by js-ipfs when running in Node.js
- [libp2p-ipfs-browser](https://github.com/ipfs/js-ipfs/blob/master/packages/ipfs/src/core/runtime/libp2p-browser.js) - libp2p configuration used by js-ipfs when running in a Browser (that supports WebRTC)
If you have developed a project using `js-libp2p`, please consider submitting your configuration to this list so that it can be found easily by other users.

View File

@ -112,13 +112,13 @@ npm install libp2p-mplex
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const node = await Libp2p.create({
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -139,7 +139,7 @@ Now that you have configured a [**Transport**][transport], [**Crypto**][crypto]
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const node = await Libp2p.create({
@ -148,7 +148,7 @@ const node = await Libp2p.create({
},
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -197,7 +197,7 @@ We can provide specific configurations for each protocol within a `config.peerDi
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const Bootstrap = require('libp2p-bootstrap')
@ -211,13 +211,13 @@ const bootstrapMultiaddrs = [
const node = await Libp2p.create({
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX],
peerDiscovery: [Bootstrap]
},
config: {
peerDiscovery: {
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minPeers)
autoDial: true, // Auto connect to discovered peers (limited by ConnectionManager minConnections)
// The `tag` property will be searched when creating the instance of your Peer Discovery service.
// The associated object, will be passed to the service when it is instantiated.
[Bootstrap.tag]: {
@ -232,9 +232,9 @@ node.on('peer:discovery', (peer) => {
console.log('Discovered %s', peer.id.toB58String()) // Log discovered peer
})
node.on('peer:connect', (peer) => {
console.log('Connected to %s', peer.id.toB58String()) // Log connected peer
})
node.connectionManager.on('peer:connect', (connection) => {
console.log('Connected to %s', connection.remotePeer.toB58String()) // Log connected peer
})
// start libp2p
await node.start()

View File

@ -161,4 +161,4 @@ const duplex = {
[it-pipe]: https://github.com/alanshaw/it-pipe
[it-pushable]: https://github.com/alanshaw/it-pushable
[it-reader]: https://github.com/alanshaw/it-reader
[streaming-iterables]: https://github.com/bustle/streaming-iterables
[streaming-iterables]: https://github.com/reconbot/streaming-iterables

View File

@ -10,6 +10,7 @@ A migration guide for refactoring your application code from libp2p v0.27.x to v
- [API Implications](#api-implications)
- [Connection Manager and Registrar](#connection-manager-and-registrar)
- [Events](#events)
- [Module Updates](#module-updates)
## PeerStore API
@ -321,6 +322,21 @@ libp2p.on('peer:discovery', (peerId) => {
})
```
## Module Updates
With `libp2p@0.28` you should update the following libp2p modules if you are relying on them:
```json
"libp2p-bootstrap": "^0.11.0",
"libp2p-delegated-content-routing": "^0.5.0",
"libp2p-delegated-peer-routing": "^0.5.0",
"libp2p-floodsub": "^0.21.0",
"libp2p-gossipsub": "^0.4.0",
"libp2p-kad-dht": "^0.19.1",
"libp2p-mdns": "^0.14.1",
"libp2p-webrtc-star": "^0.18.0"
```
[connection]: https://github.com/libp2p/js-interfaces/tree/master/src/connection
[multiaddr]: https://github.com/multiformats/js-multiaddr
[peer-id]: https://github.com/libp2p/js-peer-id

View File

@ -0,0 +1,312 @@
<!--Specify versions for migration below-->
# Migrating to libp2p@0.29
A migration guide for refactoring your application code from libp2p v0.28.x to v0.29.0.
## Table of Contents
- [API](#api)
- [Pubsub](#pubsub)
- [Uint8Arrays replace node Buffers](#uint8arrays-replace-node-buffers)
- [Module Updates](#module-updates)
## API
### Pubsub
The [`libp2p-gossipsub`](https://github.com/ChainSafe/js-libp2p-gossipsub) javascript implementation is now upgraded according to the Gossipsub v1.1 [spec](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) and it packs several security hardening extensions. You can read more about it in its [blogpost](https://blog.ipfs.io/2020-05-20-gossipsub-v1.1/).
We leveraged this update to rethink the pubsub interface, in order to make it easier, as well as to be consistent with the API of the routers. Moreover, the interface was also reconstructed to ease new pubsub router implementations.
#### Access router instance
Libp2p prior to 0.29 unnecessarily added a layer of abstraction over the pubsub routers. We now expose the pubsub router API directly and have a test suite in the [interface-pubsub](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/pubsub) to guarantee routers compliance. This enables more advanced usage of the underlying router.
**Before**
```js
libp2p.pubsub._pubsub.*
libp2p.pubsub._pubsub.topicValidators.set(topic, validator)
```
**After**
```js
libp2p.pubsub.*
libp2p.pubsub.topicValidators.set(topic, validator)
```
#### Publish
Publish uses `Uint8Array` data instead of `Buffer`.
**Before**
```js
const topic = 'topic'
const data = Buffer.from('data')
await libp2p.pubsub.publish(topic, data)
```
**After**
```js
const uint8ArrayFromString = require('uint8arrays/from-string')
const topic = 'topic'
const data = uint8ArrayFromString('data')
await libp2p.pubsub.publish(topic, data)
```
#### Subscribe
Handlers should no longer be passed when subscribing, instead, applications should bind event handlers for each topic they wish to subscribe too. This enables more flexibility at the application level without changing the underlying subscriptions.
Message data is now a `Uint8Array` instead of `Buffer`.
**Before**
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
const data = msg.data.toString()
}
libp2p.pubsub.subscribe(topic, handler)
```
**After**
```js
const uint8ArrayToString = require('uint8arrays/to-string')
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
const data = uint8ArrayToString(msg.data)
}
libp2p.pubsub.on(topic, handler)
libp2p.pubsub.subscribe(topic)
```
In the latest release, despite not being documented in `libp2p` the underlying pubsub routers supported subscribing to multiple topics at the same time. We removed that code complexity, since this is easily achieved in the application layer if needed.
**Before**
```js
const topics = ['a', 'b']
const handler = (msg) => {
// msg.data - pubsub data received
const data = msg.data.toString()
}
libp2p.pubsub.subscribe(topics, handler)
```
**After**
```js
const uint8ArrayToString = require('uint8arrays/to-string')
const topics = ['a', 'b']
const handler = (msg) => {
// msg.data - pubsub data received
const data = uint8ArrayToString(msg.data)
}
topics.forEach((topic) => {
libp2p.pubsub.on(topic, handler)
libp2p.pubsub.subscribe(topic)
})
```
#### Unsubscribe
Handlers should not be directly bound to the subscription anymore.
**Before**
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.unsubscribe(topic, handler)
```
**After**
```js
const topic = 'topic'
const handler = (msg) => {
// msg.data - pubsub data received
}
libp2p.pubsub.removeListener(topic, handler)
libp2p.pubsub.unsubscribe(topic)
```
#### Topic Validators
The validator function does not include the peer parameter anymore. It was redundant since it is included in the message and it could lead to issues as the peer that sent the message might not be the one who created the message in first place. The validator function should also throw an error instead of returning `false` when the message is not valid.
**Before**
```js
const validator = (msgTopic, peer, msg) => {
// process message
return false
}
libp2p.pubsub._pubsub.topicValidators.set(topic, validator)
```
**After**
```js
const validator = (msgTopic, msg) => {
const from = msg.from
// process message
throw new Error('not a valid message')
}
libp2p.pubsub.topicValidators.set(topic, validator)
```
### Uint8Arrays replace node Buffers
Aiming to improve libp2p browser support, we are moving away from node core modules unless we can guarantee that the code we are writing will not run in a browser. It is worth mentioning that modern JavaScript runtimes have TypedArrays such as Uint8Array backed by ArrayBuffers. All libp2p dependencies were also updated to use Uint8Array.
We use the [uint8arrays](https://www.npmjs.com/package/uint8arrays) utilities module to deal with `Uint8Arrays` easily and we recommend its usage in the application layer. Thanks for the module [@achingbrain](https://github.com/achingbrain)! It includes utilities like `compare`, `concat`, `equals`, `fromString` and `toString`. In this migration examples, we will be using the following:
```js
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
```
#### contentRouting.put
**Before**
```js
const key = '/key'
const value = Buffer.from('oh hello there')
await libp2p.contentRouting.put(key, value)
```
**After**
```js
const key = '/key'
const value = uint8ArrayFromString('oh hello there')
await libp2p.contentRouting.put(key, value)
```
#### contentRouting.get
**Before**
```js
const key = '/key'
const value = await libp2p.contentRouting.put(key)
console.log('store value is: ', value.toString())
```
**After**
```js
const key = '/key'
const value = await libp2p.contentRouting.put(key)
console.log('store value is: ', uint8ArrayToString(value))
```
#### metadataBook.set
**Before**
```js
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Saturn'))
```
**After**
```js
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Saturn'))
```
#### metadataBook.get
**Before**
```js
const data = peerStore.metadataBook.get(peerId)
console.log('stored location: ', data.get('location').toString())
```
**After**
```js
const data = peerStore.metadataBook.get(peerId)
console.log('stored location: ', uint8ArrayToString(data.get('location')))
```
#### metadataBook.getValue
**Before**
```js
const location = peerStore.metadataBook.getValue(peerId, 'location')
console.log('stored location: ', location.toString())
```
**After**
```js
const location = peerStore.metadataBook.getValue(peerId, 'location')
console.log('stored location: ', uint8ArrayToString(location))
```
#### keychain.cms.encrypt
**Before**
```js
const keyInfo = await libp2p.keychain.createKey('keyTest', 'rsa', 4096)
const enc = await libp2p.keychain.cms.encrypt('keyTest', Buffer.from('data'))
```
**After**
```js
const keyInfo = await libp2p.keychain.createKey('keyTest', 'rsa', 4096)
const enc = await libp2p.keychain.cms.encrypt('keyTest', uint8ArrayFromString('data'))
```
#### pubsub
Already specified in its own chapter above.
## Module Updates
With this release you should update the following libp2p modules if you are relying on them:
```json
"libp2p-bootstrap": "^0.12.0",
"libp2p-delegated-content-routing": "^0.6.0",
"libp2p-delegated-peer-routing": "^0.6.0",
"libp2p-floodsub": "^0.23.0",
"libp2p-gossipsub": "^0.6.0",
"libp2p-kad-dht": "^0.20.0",
"libp2p-mdns": "^0.15.0",
"libp2p-mplex": "^0.10.0",
"libp2p-noise": "^2.0.0",
"libp2p-secio": "^0.13.1",
"libp2p-tcp": "^0.15.1",
"libp2p-webrtc-star": "^0.20.0",
"libp2p-websockets": "^0.14.0",
```

View File

@ -3,7 +3,6 @@
const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets')
const mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const defaultsDeep = require('@nodeutils/defaults-deep')
const libp2p = require('../../..')
@ -17,7 +16,7 @@ class Node extends libp2p {
WS
],
streamMuxer: [ mplex ],
connEncryption: [ NOISE, SECIO ]
connEncryption: [ NOISE ]
}
}

View File

@ -1,7 +1,6 @@
'use strict'
/* eslint-disable no-console */
const multaddr = require('multiaddr')
const PeerId = require('peer-id')
const Node = require('./libp2p-bundle.js')
const { stdinToStream, streamToConsole } = require('./stream')

View File

@ -28,7 +28,7 @@ function streamToConsole(stream) {
// For each chunk of data
for await (const msg of source) {
// Output the data as a utf8 string
console.log('> ' + msg.toString('utf8').replace('\n', ''))
console.log('> ' + msg.toString().replace('\n', ''))
}
}
)

View File

@ -4,7 +4,6 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const Bootstrap = require('libp2p-bootstrap')
@ -29,7 +28,7 @@ const bootstrapers = [
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
peerDiscovery: [Bootstrap]
},
config: {

View File

@ -4,7 +4,6 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MulticastDNS = require('libp2p-mdns')
@ -16,12 +15,12 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS]
},
config: {
peerDiscovery: {
mdns: {
[MulticastDNS.tag]: {
interval: 20e3,
enabled: true
}

View File

@ -8,7 +8,7 @@ These mechanisms save configuration and enable a node to operate without any exp
## 1. Bootstrap list of Peers when booting a node
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex and SECIO. You can see the complete example at [1.js](./1.js).
For this demo, we will connect to IPFS default bootstrapper nodes and so, we will need to support the same set of features those nodes have, that are: TCP, mplex and NOISE. You can see the complete example at [1.js](./1.js).
First, we create our libp2p node.
@ -20,7 +20,7 @@ const node = Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ Bootstrap ]
},
config: {
@ -62,7 +62,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ Bootstrap ]
},
config: {
@ -130,7 +130,7 @@ const createNode = () => {
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ MulticastDNS ]
},
config: {
@ -170,5 +170,5 @@ Discovered: QmRcXXhtG8vTqwVBRonKWtV4ovDoC1Fe56WYtcrw694eiJ
There are plenty more Peer Discovery Mechanisms out there, you can:
- Find one in [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star). Yes, a transport with discovery capabilities! This happens because WebRTC requires a rendezvous point for peers to exchange [SDP](https://tools.ietf.org/html/rfc4317) offer, which means we have one or more points that can introduce peers to each other. Think of it as MulticastDNS for the Web, as in MulticastDNS only works in LAN.
- Any DHT will offer you a discovery capability. You can simple _random-walk_ the routing tables to find other peers to connect to.
- Any DHT will offer you a discovery capability. You can simple _random-walk_ the routing tables to find other peers to connect to. For example [libp2p-kad-dht](https://github.com/libp2p/js-libp2p-kad-dht) can be used for peer discovery. An example how to configure it to enable random walks can be found [here](https://github.com/libp2p/js-libp2p/blob/v0.28.4/doc/CONFIGURATION.md#customizing-dht).
- You can create your own Discovery service, a registry, a list, a radio beacon, you name it!

View File

@ -3,7 +3,6 @@
const TCP = require('libp2p-tcp')
const WS = require('libp2p-websockets')
const mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const defaultsDeep = require('@nodeutils/defaults-deep')
@ -18,7 +17,7 @@ class Node extends libp2p {
WS
],
streamMuxer: [ mplex ],
connEncryption: [ NOISE, SECIO ]
connEncryption: [ NOISE ]
}
}

View File

@ -4,7 +4,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@ -8,31 +8,24 @@ A byproduct of having these encrypted communications modules is that we can auth
# 1. Set up encrypted communications
We will build this example on top of example for [Protocol and Stream Multiplexing](../protocol-and-stream-multiplexing). You will need the modules `libp2p-secio`<sup>*</sup> and `libp2p-noise` to complete it, go ahead and `npm install libp2p-secio libp2p-noise`.
We will build this example on top of example for [Protocol and Stream Multiplexing](../protocol-and-stream-multiplexing). You will need the `libp2p-noise` module to complete it, go ahead and `npm install libp2p-noise`.
To add them to your libp2p configuration, all you have to do is:
```JavaScript
const Libp2p = require('libp2p')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const createNode = () => {
return Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
// Attach secio as the crypto channel to use
connEncryption: [ NOISE, SECIO ]
// Attach noise as the crypto channel to use
connEncryption: [ NOISE ]
}
})
}
```
And that's it, from now on, all your libp2p communications are encrypted. Try running the example [1.js](./1.js) to see it working.
_<sup>*</sup> SECIO is the crypto channel developed for IPFS, it is a TLS 1.3 like crypto channel that established an encrypted communication channel between two peers._
If you want to want to learn more about how SECIO works, you can read the [great write up done by Dominic Tarr](https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel).
Important note: SECIO hasn't been audited and so, we do not recommend to trust its security. We intent to move to TLS 1.3 once the specification is finalized and an implementation exists that we can use.

View File

@ -11,26 +11,9 @@ cd ./examples/libp2p-in-the-browser
npm install
```
## Signaling Server
This example uses the `libp2p-webrtc-star` module, which enables libp2p browser nodes to establish direct connections to one another via a central signaling server. For this example, we are using the signaling server that ships with `libp2p-webrtc-star`.
You can start the server by running `npm run server`. This will start a signaling server locally on port `9090`. If you'd like to run a signaling server outside of this example, you can see instructions on how to do so in the [`libp2p-webrtc-star` README](https://github.com/libp2p/js-libp2p-webrtc-star).
When you run the server, you should see output that looks something like this:
```log
$ npm run server
> libp2p-in-browser@1.0.0 server
> star-signal
Listening on: http://0.0.0.0:9090
```
## Running the examples
Once you have started the signaling server, you can run the Parcel server.
Start by running the Parcel server:
```
npm start
@ -53,3 +36,11 @@ This will compile the code and start a server listening on port [http://localhos
Now, if you open a second browser tab to `http://localhost:1234`, you should discover your node from the previous tab. This is due to the fact that the `libp2p-webrtc-star` transport also acts as a Peer Discovery interface. Your node will be notified of any peer that connects to the same signaling server you are connected to. Once libp2p discovers this new peer, it will attempt to establish a direct WebRTC connection.
**Note**: In the example we assign libp2p to `window.libp2p`, in case you would like to play around with the API directly in the browser. You can of course make changes to `index.js` and Parcel will automatically rebuild and reload the browser tabs.
## Going to production?
This example uses public `libp2p-webrtc-star` servers. These servers should be used for experimenting and demos, they **MUST** not be used in production as there is no guarantee on availability.
You can see how to deploy your own signaling server in [libp2p/js-libp2p-webrtc-star/DEPLOYMENT.md](https://github.com/libp2p/js-libp2p-webrtc-star/blob/master/DEPLOYMENT.md).
Once you have your own server running, you should add its listen address in your libp2p node configuration.

View File

@ -3,9 +3,8 @@ import Libp2p from 'libp2p'
import Websockets from 'libp2p-websockets'
import WebRTCStar from 'libp2p-webrtc-star'
import { NOISE } from 'libp2p-noise'
import Secio from 'libp2p-secio'
import Mplex from 'libp2p-mplex'
import Boostrap from 'libp2p-bootstrap'
import Bootstrap from 'libp2p-bootstrap'
document.addEventListener('DOMContentLoaded', async () => {
// Create our libp2p node
@ -14,17 +13,22 @@ document.addEventListener('DOMContentLoaded', async () => {
// Add the signaling server address, along with our PeerId to our multiaddrs list
// libp2p will automatically attempt to dial to the signaling server so that it can
// receive inbound connections from other peers
listen: ['/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star']
listen: [
'/dns4/wrtc-star1.par.dwebops.pub/tcp/443/wss/p2p-webrtc-star',
'/dns4/wrtc-star2.sjc.dwebops.pub/tcp/443/wss/p2p-webrtc-star'
]
},
modules: {
transport: [Websockets, WebRTCStar],
connEncryption: [NOISE, Secio],
connEncryption: [NOISE],
streamMuxer: [Mplex],
peerDiscovery: [Boostrap]
peerDiscovery: [Bootstrap]
},
config: {
peerDiscovery: {
bootstrap: {
// The `tag` property will be searched when creating the instance of your Peer Discovery service.
// The associated object, will be passed to the service when it is instantiated.
[Bootstrap.tag]: {
enabled: true,
list: [
'/dns4/ams-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',

View File

@ -8,8 +8,7 @@
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "parcel index.html",
"server": "star-signal"
"start": "parcel index.html"
},
"keywords": [],
"author": "",
@ -17,12 +16,11 @@
"dependencies": {
"@babel/preset-env": "^7.8.3",
"libp2p": "../../",
"libp2p-bootstrap": "^0.11",
"libp2p-mplex": "^0.9.3",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.2",
"libp2p-webrtc-star": "^0.18.0",
"libp2p-websockets": "^0.13.2"
"libp2p-bootstrap": "^0.12.1",
"libp2p-mplex": "^0.10.0",
"libp2p-noise": "^2.0.0",
"libp2p-webrtc-star": "^0.20.0",
"libp2p-websockets": "^0.14.0"
},
"devDependencies": {
"@babel/cli": "^7.8.3",

View File

@ -5,7 +5,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const KadDHT = require('libp2p-kad-dht')
const delay = require('delay')
@ -18,7 +17,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
dht: KadDHT
},
config: {

View File

@ -4,7 +4,6 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const CID = require('cids')
const KadDHT = require('libp2p-kad-dht')
@ -20,7 +19,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
dht: KadDHT
},
config: {

View File

@ -23,7 +23,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},

View File

@ -2,11 +2,7 @@
This example shows how to set up a private network of libp2p nodes.
## Setup
Install dependencies:
```
npm install
```
1. Install the modules in the libp2p root directory, `npm install`.
## Run
Running the example will cause two nodes with the same swarm key to be started and exchange basic information.

View File

@ -1,18 +1,17 @@
/* eslint no-console: ["off"] */
'use strict'
const { Buffer } = require('buffer')
const { generate } = require('libp2p/src/pnet')
const privateLibp2pNode = require('./libp2p-node')
const pipe = require('it-pipe')
// Create a buffer and write the swarm key to it
const swarmKey = Buffer.alloc(95)
// Create a Uint8Array and write the swarm key to it
const swarmKey = new Uint8Array(95)
generate(swarmKey)
// This key is for testing a different key not working
const otherSwarmKey = Buffer.alloc(95)
const otherSwarmKey = new Uint8Array(95)
generate(otherSwarmKey)
;(async () => {

View File

@ -3,7 +3,6 @@
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const Protector = require('libp2p/src/pnet')
@ -11,7 +10,7 @@ const Protector = require('libp2p/src/pnet')
* privateLibp2pNode returns a libp2p node function that will use the swarm
* key with the given `swarmKey` to create the Protector
*
* @param {Buffer} swarmKey
* @param {Uint8Array} swarmKey
* @returns {Promise<libp2p>} Returns a libp2pNode function for use in IPFS creation
*/
const privateLibp2pNode = async (swarmKey) => {
@ -24,7 +23,7 @@ const privateLibp2pNode = async (swarmKey) => {
streamMuxer: [MPLEX], // We're only using mplex muxing
// Let's make sure to use identifying crypto in our pnet since the protector doesn't
// care about node identity, and only the presence of private keys
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
// Leave peer discovery empty, we don't want to find peers. We could omit the property, but it's
// being left in for explicit readability.
// We should explicitly dial pnet peers, or use a custom discovery service for finding nodes in our pnet

View File

@ -1,20 +0,0 @@
{
"name": "pnet-ipfs-example",
"version": "1.0.0",
"description": "An example of private networking with IPFS",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"libp2p": "../..",
"libp2p-mplex": "^0.9.3",
"libp2p-noise": "^1.1.0",
"libp2p-secio": "^0.12.1",
"libp2p-tcp": "^0.14.2"
}
}

View File

@ -4,7 +4,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@ -4,7 +4,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@ -16,7 +15,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@ -5,7 +5,6 @@ const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const pipe = require('it-pipe')
@ -17,7 +16,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@ -1,13 +1,13 @@
/* eslint-disable no-console */
'use strict'
const { Buffer } = require('buffer')
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const Gossipsub = require('libp2p-gossipsub')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
const createNode = async () => {
const node = await Libp2p.create({
@ -17,7 +17,7 @@ const createNode = async () => {
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
pubsub: Gossipsub
}
})
@ -38,16 +38,18 @@ const createNode = async () => {
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
node1.pubsub.on(topic, (msg) => {
console.log(`node1 received: ${uint8ArrayToString(msg.data)}`)
})
await node1.pubsub.subscribe(topic)
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
node2.pubsub.on(topic, (msg) => {
console.log(`node2 received: ${uint8ArrayToString(msg.data)}`)
})
await node2.pubsub.subscribe(topic)
// node2 publishes "news" every second
setInterval(() => {
node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!'))
node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!'))
}, 1000)
})()

View File

@ -27,7 +27,7 @@ const node = await Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE, SECIO ],
connEncryption: [ NOISE ],
// we add the Pubsub module we want
pubsub: Gossipsub
}
@ -47,17 +47,19 @@ node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
node1.pubsub.on(topic, (msg) => {
console.log(`node1 received: ${uint8ArrayToString(msg.data)}`)
})
await node1.pubsub.subscribe(topic)
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
node2.pubsub.on(topic, (msg) => {
console.log(`node2 received: ${uint8ArrayToString(msg.data)}`)
})
await node2.pubsub.subscribe(topic)
// node2 publishes "news" every second
setInterval(() => {
node2.pubsub.publish(topic, Buffer.from('Bird bird bird, bird is the word!'))
node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!'))
}, 1000)
```

View File

@ -0,0 +1,87 @@
/* eslint-disable no-console */
'use strict'
const Libp2p = require('../../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const Gossipsub = require('libp2p-gossipsub')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [NOISE],
pubsub: Gossipsub
}
})
await node.start()
return node
}
(async () => {
const topic = 'fruit'
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode(),
])
// node1 conect to node2 and node2 conect to node3
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node2.dial(node3.peerId)
//subscribe
node1.pubsub.on(topic, (msg) => {
console.log(`node1 received: ${uint8ArrayToString(msg.data)}`)
})
await node1.pubsub.subscribe(topic)
node2.pubsub.on(topic, (msg) => {
console.log(`node2 received: ${uint8ArrayToString(msg.data)}`)
})
await node2.pubsub.subscribe(topic)
node3.pubsub.on(topic, (msg) => {
console.log(`node3 received: ${uint8ArrayToString(msg.data)}`)
})
await node3.pubsub.subscribe(topic)
const validateFruit = (msgTopic, msg) => {
const fruit = uint8ArrayToString(msg.data)
const validFruit = ['banana', 'apple', 'orange']
if (!validFruit.includes(fruit)) {
throw new Error('no valid fruit received')
}
}
//validate fruit
node1.pubsub.topicValidators.set(topic, validateFruit)
node2.pubsub.topicValidators.set(topic, validateFruit)
node3.pubsub.topicValidators.set(topic, validateFruit)
// node1 publishes "fruits" every five seconds
var count = 0;
const myFruits = ['banana', 'apple', 'car', 'orange'];
// car is not a fruit !
setInterval(() => {
console.log('############## fruit ' + myFruits[count] + ' ##############')
node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count]))
count++
if (count == myFruits.length) {
count = 0
}
}, 5000)
})()

View File

@ -0,0 +1,113 @@
# Filter Messages
To prevent undesired data from being propagated on the network, we can apply a filter to Gossipsub. Messages that fail validation in the filter will not be re-shared.
## 1. Setting up a PubSub network with three nodes
First, let's update our libp2p configuration with a pubsub implementation.
```JavaScript
const Libp2p = require('libp2p')
const Gossipsub = require('libp2p-gossipsub')
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ NOISE ],
pubsub: Gossipsub
}
})
```
Then, create three nodes and connect them together. In this example, we will connect the nodes in series. Node 1 connected with node 2 and node 2 connected with node 3.
```JavaScript
const [node1, node2, node3] = await Promise.all([
createNode(),
createNode(),
createNode(),
])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await node2.dial(node3.peerId)
```
Now we' can subscribe to the fruit topic and log incoming messages.
```JavaScript
const topic = 'fruit'
node1.pubsub.on(topic, (msg) => {
console.log(`node1 received: ${uint8ArrayToString(msg.data)}`)
})
await node1.pubsub.subscribe(topic)
node2.pubsub.on(topic, (msg) => {
console.log(`node2 received: ${uint8ArrayToString(msg.data)}`)
})
await node2.pubsub.subscribe(topic)
node3.pubsub.on(topic, (msg) => {
console.log(`node3 received: ${uint8ArrayToString(msg.data)}`)
})
await node3.pubsub.subscribe(topic)
```
Finally, let's define the additional filter in the fruit topic.
```JavaScript
const validateFruit = (msgTopic, msg) => {
const fruit = uint8ArrayToString(msg.data)
const validFruit = ['banana', 'apple', 'orange']
if (!validFruit.includes(fruit)) {
throw new Error('no valid fruit received')
}
}
node1.pubsub.topicValidators.set(topic, validateFruit)
node2.pubsub.topicValidators.set(topic, validateFruit)
node3.pubsub.topicValidators.set(topic, validateFruit)
```
In this example, node one has an outdated version of the system, or is a malicious node. When it tries to publish fruit, the messages are re-shared and all the nodes share the message. However, when it tries to publish a vehicle the message is not re-shared.
```JavaScript
var count = 0;
const myFruits = ['banana', 'apple', 'car', 'orange'];
setInterval(() => {
console.log('############## fruit ' + myFruits[count] + ' ##############')
node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count]))
count++
if (count == myFruits.length) {
count = 0
}
}, 5000)
```
Result
```
> node 1.js
############## fruit banana ##############
node1 received: banana
node2 received: banana
node3 received: banana
############## fruit apple ##############
node1 received: apple
node2 received: apple
node3 received: apple
############## fruit car ##############
node1 received: car
############## fruit orange ##############
node1 received: orange
node2 received: orange
node3 received: orange
```

View File

@ -4,18 +4,17 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signall the addresses we want to be available, we use
// To signal the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
connEncryption: [NOISE, SECIO]
connEncryption: [NOISE]
}
})

View File

@ -4,7 +4,6 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex')
const pipe = require('it-pipe')
@ -13,13 +12,13 @@ const concat = require('it-concat')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signall the addresses we want to be available, we use
// To signal the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})

View File

@ -5,7 +5,6 @@ const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const WebSockets = require('libp2p-websockets')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const MPLEX = require('libp2p-mplex')
const pipe = require('it-pipe')
@ -17,11 +16,11 @@ const createNode = async (transports, addresses = []) => {
const node = await Libp2p.create({
addresses: {
listen: addresses.map((a) => a)
listen: addresses
},
modules: {
transport: transports,
connEncryption: [NOISE, SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})

View File

@ -13,10 +13,10 @@ When using libp2p, you need properly configure it, that is, pick your set of mod
You will need 4 dependencies total, so go ahead and install all of them with:
```bash
> npm install libp2p libp2p-tcp libp2p-secio peer-info
> npm install libp2p libp2p-tcp libp2p-noise
```
Then, on your favorite text editor create a file with the `.js` extension. I've called mine `1.js`.
Then, in your favorite text editor create a file with the `.js` extension. I've called mine `1.js`.
First thing is to create our own libp2p node! Insert:
@ -26,18 +26,17 @@ First thing is to create our own libp2p node! Insert:
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const { NOISE } = require('libp2p-noise')
const SECIO = require('libp2p-secio')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signall the addresses we want to be available, we use
// To signal the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [ TCP ],
connEncryption: [ NOISE, SECIO ]
connEncryption: [ NOISE ]
}
})
@ -78,20 +77,41 @@ That `QmW2cKTakTYqbQkUzBTEGXgWYFj1YEPeUndE1YWs6CBzDQ` is the PeerId that was cre
Now that we have our `createNode` function, let's create two nodes and make them dial to each other! You can find the complete solution at [2.js](./2.js).
For this step, we will need one more dependency.
For this step, we will need some more dependencies.
```bash
> npm install it-pipe it-buffer
> npm install it-pipe it-concat libp2p-mplex
```
And we also need to import the module on our .js file:
And we also need to import the modules on our .js file:
```js
const pipe = require('it-pipe')
const { toBuffer } = require('it-buffer')
const concat = require('it-concat')
const MPLEX = require('libp2p-mplex')
```
We are going to reuse the `createNode` function from step 1, but this time to make things simpler, we will create another function to print the addrs to avoid duplicating code.
We are going to reuse the `createNode` function from step 1, but this time add a stream multiplexer from `libp2p-mplex`.
```js
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
// To signal the addresses we want to be available, we use
// the multiaddr format, a self describable address
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
connEncryption: [NOISE],
streamMuxer: [MPLEX] // <--- Add this line
}
})
await node.start()
return node
}
```
We will also make things simpler by creating another function to print the multiaddresses to avoid duplicating code.
```JavaScript
function printAddrs (node, number) {
@ -100,7 +120,7 @@ function printAddrs (node, number) {
}
```
Then,
Then add,
```js
;(async () => {
@ -112,18 +132,15 @@ Then,
printAddrs(node1, '1')
printAddrs(node2, '2')
node2.handle('/print', ({ stream }) => {
pipe(
node2.handle('/print', async ({ stream }) => {
const result = await pipe(
stream,
async function (source) {
for await (const msg of source) {
console.log(msg.toString())
}
}
concat
)
console.log(result.toString())
})
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
@ -132,8 +149,9 @@ node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
)
})();
```
For more information refer to the [docs](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md).
The result should be look like:
The result should look like:
```bash
> node 2.js
@ -148,33 +166,33 @@ Hello p2p world!
## 3. Using multiple transports
Next, we want to be available in multiple transports to increase our chances of having common transports in the network. A simple scenario, a node running in the browser only has access to HTTP, WebSockets and WebRTC since the browser doesn't let you open any other kind of transport, for this node to dial to some other node, that other node needs to share a common transport.
Next, we want nodes to have multiple transports available to increase their chances of having a common transport in the network to communicate over. A simple scenario is a node running in the browser only having access to HTTP, WebSockets and WebRTC since the browser doesn't let you open any other kind of transport. For this node to dial to some other node, that other node needs to share a common transport.
What we are going to do in this step is to create 3 nodes, one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](./3.js).
What we are going to do in this step is to create 3 nodes: one with TCP, another with TCP+WebSockets and another one with just WebSockets. The full solution can be found on [3.js](./3.js).
In this example, we will need to also install `libp2p-websockets`, go ahead and install:
In this example, we will need to also install `libp2p-websockets`:
```bash
> npm install libp2p-websockets
```
We want to create 3 nodes, one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `createNode` function to contemplate WebSockets as well. Moreover, let's upgrade our function to enable us to pick the addrs in which a node will start a listener:
We want to create 3 nodes: one with TCP, one with TCP+WebSockets and one with just WebSockets. We need to update our `createNode` function to accept WebSocket connections as well. Moreover, let's upgrade our function to enable us to pick the addresses over which a node will start a listener:
```JavaScript
// ...
const createNode = async (transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
const createNode = async (transports, addresses = []) => {
if (!Array.isArray(addresses)) {
addresses = [addresses]
}
const node = await Libp2p.create({
addresses: {
listen: multiaddrs.map((a) => multiaddr(a))
listen: addresses
},
modules: {
transport: transports,
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -232,7 +250,7 @@ try {
}
```
`print` is a function created using the code from 2.js, but factored into its own function to save lines, here it is:
`print` is a function that prints each piece of data from a stream onto a new line but factored into its own function to save lines:
```JavaScript
function print ({ stream }) {
@ -247,7 +265,7 @@ function print ({ stream }) {
}
```
If everything was set correctly, you now should see the following after you run the script:
If everything was set correctly, you now should see something similar to the following after running the script:
```Bash
> node 3.js
@ -266,13 +284,13 @@ node 3 failed to dial to node 1 with:
Error: No transport available for address /ip4/127.0.0.1/tcp/51482
```
As expected, we created 3 nodes, node 1 with TCP, node 2 with TCP+WebSockets and node 3 with just WebSockets. node 1 -> node 2 and node 2 -> node 3 managed to dial correctly because they shared a common transport, however, node 3 -> node 1 failed because they didn't share any.
As expected, we created 3 nodes: node 1 with TCP, node 2 with TCP+WebSockets and node 3 with just WebSockets. node 1 -> node 2 and node 2 -> node 3 managed to dial correctly because they shared a common transport; however, node 3 -> node 1 failed because they didn't share any.
## 4. How to create a new libp2p transport
Today there are already several transports available and plenty to come, you can find these at [interface-transport implementations](https://github.com/libp2p/js-interfaces/tree/master/src/transport#modules-that-implement-the-interface) list.
Today there are already several transports available and plenty to come. You can find these at [interface-transport implementations](https://github.com/libp2p/js-interfaces/tree/master/src/transport#modules-that-implement-the-interface) list.
Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities, but as far as libp2p is concerned, if it follows the interface defined at the [spec](https://github.com/libp2p/js-interfaces/tree/master/src/transport#api) it will be able to use it.
Adding more transports is done through the same way as you added TCP and WebSockets. Some transports might offer extra functionalities, but as far as libp2p is concerned, if it follows the interface defined in the [spec](https://github.com/libp2p/js-interfaces/tree/master/src/transport#api) it will be able to use it.
If you decide to implement a transport yourself, please consider adding to the list so that others can use it as well.

View File

@ -51,7 +51,6 @@
["libp2p/js-peer-id", "peer-id"],
"pubsub",
["libp2p/js-libp2p-pubsub", "libp2p-pubsub"],
["libp2p/js-libp2p-floodsub", "libp2p-floodsub"],
["ChainSafe/js-libp2p-gossipsub", "libp2p-gossipsub"],

View File

@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.28.2",
"version": "0.29.1",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
@ -37,7 +37,7 @@
"homepage": "https://libp2p.io",
"license": "MIT",
"engines": {
"node": ">=10.0.0",
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
"dependencies": {
@ -50,7 +50,7 @@
"err-code": "^2.0.0",
"events": "^3.1.0",
"hashlru": "^2.3.0",
"interface-datastore": "^1.0.4",
"interface-datastore": "^2.0.0",
"ipfs-utils": "^2.2.0",
"it-all": "^1.0.1",
"it-buffer": "^0.1.2",
@ -58,81 +58,82 @@
"it-length-prefixed": "^3.0.1",
"it-pipe": "^1.1.0",
"it-protocol-buffers": "^0.2.0",
"libp2p-crypto": "^0.17.6",
"libp2p-interfaces": "^0.3.0",
"libp2p-utils": "^0.1.2",
"mafmt": "^7.0.0",
"libp2p-crypto": "^0.18.0",
"libp2p-interfaces": "^0.5.1",
"libp2p-utils": "^0.2.0",
"mafmt": "^8.0.0",
"merge-options": "^2.0.0",
"moving-average": "^1.0.0",
"multiaddr": "^7.4.3",
"multistream-select": "^0.15.0",
"multiaddr": "^8.0.0",
"multicodec": "^2.0.0",
"multistream-select": "^1.0.0",
"mutable-proxy": "^1.0.0",
"node-forge": "^0.9.1",
"p-any": "^3.0.0",
"p-fifo": "^1.0.0",
"p-settle": "^4.0.1",
"peer-id": "^0.13.11",
"protons": "^1.0.1",
"peer-id": "^0.14.2",
"protons": "^2.0.0",
"retimer": "^2.0.0",
"sanitize-filename": "^1.6.3",
"streaming-iterables": "^4.1.0",
"timeout-abort-controller": "^1.0.0",
"streaming-iterables": "^5.0.2",
"timeout-abort-controller": "^1.1.1",
"varint": "^5.0.0",
"xsalsa20": "^1.0.2"
},
"devDependencies": {
"@nodeutils/defaults-deep": "^1.1.0",
"abortable-iterator": "^3.0.0",
"aegir": "^22.0.0",
"aegir": "^27.0.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2",
"chai-string": "^1.5.0",
"cids": "^0.8.0",
"datastore-fs": "^1.1.0",
"datastore-level": "^1.1.0",
"cids": "^1.0.0",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"interop-libp2p": "^0.1.0",
"ipfs-http-client": "^44.0.0",
"interop-libp2p": "^0.3.0",
"ipfs-http-client": "^47.0.1",
"it-concat": "^1.0.0",
"it-pair": "^1.0.0",
"it-pushable": "^1.4.0",
"level": "^6.0.1",
"libp2p-bootstrap": "^0.11.0",
"libp2p-delegated-content-routing": "^0.5.0",
"libp2p-delegated-peer-routing": "^0.5.0",
"libp2p-floodsub": "^0.21.0",
"libp2p-gossipsub": "^0.4.0",
"libp2p-kad-dht": "^0.19.1",
"libp2p-mdns": "^0.14.1",
"libp2p-mplex": "^0.9.5",
"libp2p-noise": "^1.1.1",
"libp2p-secio": "^0.12.4",
"libp2p-tcp": "^0.14.1",
"libp2p-webrtc-star": "^0.18.0",
"libp2p-websockets": "^0.13.1",
"multihashes": "^0.4.19",
"nock": "^12.0.3",
"libp2p": ".",
"libp2p-bootstrap": "^0.12.0",
"libp2p-delegated-content-routing": "^0.7.0",
"libp2p-delegated-peer-routing": "^0.7.0",
"libp2p-floodsub": "^0.23.0",
"libp2p-gossipsub": "^0.6.0",
"libp2p-kad-dht": "^0.20.0",
"libp2p-mdns": "^0.15.0",
"libp2p-mplex": "^0.10.1",
"libp2p-noise": "^2.0.0",
"libp2p-secio": "^0.13.1",
"libp2p-tcp": "^0.15.1",
"libp2p-webrtc-star": "^0.20.0",
"libp2p-websockets": "^0.14.0",
"multihashes": "^3.0.1",
"nock": "^13.0.3",
"p-defer": "^3.0.0",
"p-times": "^3.0.0",
"p-wait-for": "^3.1.0",
"promisify-es6": "^1.0.3",
"rimraf": "^3.0.2",
"sinon": "^9.0.2"
"sinon": "^9.0.2",
"uint8arrays": "^1.1.0"
},
"contributors": [
"David Dias <daviddias.p@gmail.com>",
"Jacob Heun <jacobheun@gmail.com>",
"Vasco Santos <vasco.santos@moxy.studio>",
"Alan Shaw <alan@tableflip.io>",
"Alex Potsides <alex@achingbrain.net>",
"Cayman <caymannava@gmail.com>",
"Pedro Teixeira <i@pgte.me>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Alex Potsides <alex@achingbrain.net>",
"Maciej Krüger <mkg20001@gmail.com>",
"Hugo Dias <mail@hugodias.me>",
"Volker Mische <volker.mische@gmail.com>",
"dirkmc <dirkmdev@gmail.com>",
"Volker Mische <volker.mische@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"Elven <mon.samuel@qq.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
@ -141,26 +142,31 @@
"Thomas Eizinger <thomas@eizinger.io>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Didrik Nordström <didrik@betamos.se>",
"Henrique Dias <hacdias@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"Irakli Gozalishvili <rfobic@gmail.com>",
"Ethan Lam <elmemphis2000@gmail.com>",
"Joel Gustafson <joelg@mit.edu>",
"Julien Bouquillon <contact@revolunet.com>",
"Kevin Kwok <antimatter15@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Soeren <nikorpoulsen@gmail.com>",
"Sönke Hahn <soenkehahn@gmail.com>",
"Tiago Alves <alvesjtiago@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"Cindy Wu <ciindy.wu@gmail.com>",
"Chris Bratlien <chrisbratlien@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"Bernd Strehl <bernd.strehl@gmail.com>",
"isan_rivkin <isanrivkin@gmail.com>",
"Henrique Dias <hacdias@gmail.com>",
"Irakli Gozalishvili <rfobic@gmail.com>"
"Florian-Merle <florian.david.merle@gmail.com>",
"Francis Gulotta <wizard@roborooter.com>",
"Felipe Martins <felipebrasil93@gmail.com>"
]
}

View File

@ -15,11 +15,11 @@ const multiaddr = require('multiaddr')
*/
class AddressManager {
/**
* @constructor
* @class
* @param {object} [options]
* @param {Array<string>} [options.listen = []] list of multiaddrs string representation to listen.
* @param {Array<string>} [options.announce = []] list of multiaddrs string representation to announce.
* @param {Array<string>} [options.noAnnounce = []] list of multiaddrs string representation to not announce.
* @param {Array<string>} [options.listen = []] - list of multiaddrs string representation to listen.
* @param {Array<string>} [options.announce = []] - list of multiaddrs string representation to announce.
* @param {Array<string>} [options.noAnnounce = []] - list of multiaddrs string representation to not announce.
*/
constructor ({ listen = [], announce = [], noAnnounce = [] } = {}) {
this.listen = new Set(listen)
@ -29,7 +29,8 @@ class AddressManager {
/**
* Get peer listen multiaddrs.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
getListenAddrs () {
return Array.from(this.listen).map((a) => multiaddr(a))
@ -37,7 +38,8 @@ class AddressManager {
/**
* Get peer announcing multiaddrs.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
getAnnounceAddrs () {
return Array.from(this.announce).map((a) => multiaddr(a))
@ -45,7 +47,8 @@ class AddressManager {
/**
* Get peer noAnnouncing multiaddrs.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
getNoAnnounceAddrs () {
return Array.from(this.noAnnounce).map((a) => multiaddr(a))

View File

@ -1,12 +1,20 @@
# js-libp2p-circuit
> Node.js implementation of the Circuit module that libp2p uses, which implements the [interface-connection](https://github.com/libp2p/js-interfaces/tree/master/src/connection) interface for dial/listen.
> Node.js implementation of the Circuit module that libp2p uses, which implements the [interface-connection](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/connection) interface for dial/listen.
**Note**: git history prior to merging into js-libp2p can be found in the original repository, https://github.com/libp2p/js-libp2p-circuit.
`libp2p-circuit` implements the circuit-relay mechanism that allows nodes that don't speak the same protocol to communicate using a third _relay_ node.
`libp2p-circuit` implements the circuit-relay mechanism that allows nodes that don't speak the same protocol to communicate using a third _relay_ node. You can read more about this in its [spec](https://github.com/libp2p/specs/tree/master/relay).
This module uses [pull-streams](https://pull-stream.github.io) for all stream based interfaces.
## Table of Contents
- [js-libp2p-circuit](#js-libp2p-circuit)
- [Why?](#why)
- [libp2p-circuit and IPFS](#libp2p-circuit-and-ipfs)
- [Table of Contents](#table-of-contents)
- [Usage](#usage)
- [API](#api)
- [Implementation rational](#implementation-rational)
### Why?
@ -16,77 +24,42 @@ The use of circuit-relaying is not limited to routing traffic between browser no
- routing traffic between private nets and circumventing NAT layers
- route mangling for better privacy (matreshka/shallot dialing).
It's also possible to use it for clients that implement exotic transports such as devices that only have bluetooth radios to be reachable over bluetooth enabled relays and become full p2p nodes.
It's also possible to use it for clients that implement exotic transports such as devices that only have bluetooth radios to be reachable over bluetooth enabled relays and become full p2p nodes.
### libp2p-circuit and IPFS
Prior to `libp2p-circuit` there was a rift in the IPFS network, were IPFS nodes could only access content from nodes that speak the same protocol, for example TCP only nodes could only dial to other TCP only nodes, same for any other protocol combination. In practice, this limitation was most visible in JS-IPFS browser nodes, since they can only dial out but not be dialed in over WebRTC or WebSockets, hence any content that the browser node held was not reachable by the rest of the network even through it was announced on the DHT. Non browser IPFS nodes would usually speak more than one protocol such as TCP, WebSockets and/or WebRTC, this made the problem less severe outside of the browser. `libp2p-circuit` solves this problem completely, as long as there are `relay nodes` capable of routing traffic between those nodes their content should be available to the rest of the IPFS network.
## Table of Contents
- [js-libp2p-circuit](#js-libp2p-circuit)
- [Why?](#why)
- [libp2p-circuit and IPFS](#libp2p-circuit-and-ipfs)
- [Table of Contents](#table-of-contents)
- [Usage](#usage)
- [Example](#example)
- [Create dialer/listener](#create-dialerlistener)
- [Create `relay`](#create-relay)
- [API](#api)
- [Implementation rational](#implementation-rational)
## Usage
### Example
Libp2p circuit configuration can be seen at [Setup with Relay](../../doc/CONFIGURATION.md#setup-with-relay).
#### Create dialer/listener
Once you have a circuit relay node running, you can configure other nodes to use it as a relay as follows:
```js
const Circuit = require('libp2p-circuit')
const multiaddr = require('multiaddr')
const pull = require('pull-stream')
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const mh1 = multiaddr('/p2p-circuit/p2p/QmHash') // dial /p2p/QmHash over any circuit
const relayAddr = ...
const circuit = new Circuit(swarmInstance, options) // pass swarm instance and options
const listener = circuit.createListener(mh1, (connection) => {
console.log('new connection opened')
pull(
pull.values(['hello']),
socket
)
const node = await Libp2p.create({
addresses: {
listen: [multiaddr(`${relayAddr}/p2p-circuit`)]
},
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE]
},
config: {
relay: { // Circuit Relay options (this config is part of libp2p core configurations)
enabled: true // Allows you to dial and accept relayed connections. Does not make you a relay.
}
}
})
listener.listen(() => {
console.log('listening')
pull(
circuit.dial(mh1),
pull.log,
pull.onEnd(() => {
circuit.close()
})
)
})
```
Outputs:
```sh
listening
new connection opened
hello
```
#### Create `relay`
```js
const Relay = require('libp2p-circuit').Relay
const relay = new Relay(options)
relay.mount(swarmInstance) // start relaying traffic
```
## API
@ -101,7 +74,7 @@ Both for dialing and listening.
### Implementation rational
This module is not a transport, however it implements `interface-transport` interface in order to allow circuit to be plugged with `libp2p-swarm`. The rational behind it is that, `libp2p-circuit` has a dial and listen flow, which fits nicely with other transports, moreover, it requires the _raw_ connection to be encrypted and muxed just as a regular transport's connection does. All in all, `interface-transport` ended up being the correct level of abstraction for circuit, as well as allowed us to reuse existing integration points in `libp2p-swarm` and `libp2p` without adding any ad-hoc logic. All parts of `interface-transport` are used, including `.getAddr` which returns a list of `/p2p-circuit` addresses that circuit is currently listening.
This module is not a transport, however it implements `interface-transport` interface in order to allow circuit to be plugged with `libp2p`. The rational behind it is that, `libp2p-circuit` has a dial and listen flow, which fits nicely with other transports, moreover, it requires the _raw_ connection to be encrypted and muxed just as a regular transport's connection does. All in all, `interface-transport` ended up being the correct level of abstraction for circuit, as well as allowed us to reuse existing integration points in `libp2p` and `libp2p` without adding any ad-hoc logic. All parts of `interface-transport` are used, including `.getAddr` which returns a list of `/p2p-circuit` addresses that circuit is currently listening.
```
libp2p libp2p-circuit (transport)
@ -109,13 +82,13 @@ libp2p
| +---------------------------------+ | | |
| | | | | +------------------+ |
| | | | circuit-relay listens for the HOP | | | |
| | libp2p-swarm <------------------------------------------------| circuit-relay | |
| | libp2p <------------------------------------------------| circuit-relay | |
| | | | message to handle incomming relay | | | |
| | | | requests from other nodes | +------------------+ |
| +---------------------------------+ | | |
| ^ ^ ^ ^ ^ ^ | | +------------------+ |
| | | | | | | | | | +-------------+ | |
| | | | | | | | dialer uses libp2p-swarm to dial | | | | | |
| | | | | | | | dialer uses libp2p to dial | | | | | |
| | | | +----------------------------------------------------------------------> dialer | | |
| | | transports | | to a circuit-relay node using the | | | | | |
| | | | | | | HOP message | | +-------------+ | |

View File

@ -90,9 +90,8 @@ module.exports.handleHop = async function handleHop ({
* peer. A new, virtual, connection will be created between the two via the relay.
*
* @param {object} options
* @param {Connection} options.connection Connection to the relay
* @param {Connection} options.connection - Connection to the relay
* @param {*} options.request
* @param {Circuit} options.circuit
* @returns {Promise<Connection>}
*/
module.exports.hop = async function hop ({
@ -119,6 +118,11 @@ module.exports.hop = async function hop ({
/**
* Creates an unencoded CAN_HOP response based on the Circuits configuration
*
* @param {Object} options
* @param {Connection} options.connection
* @param {StreamHandler} options.streamHandler
* @param {Circuit} options.circuit
* @private
*/
module.exports.handleCanHop = function handleCanHop ({

View File

@ -15,7 +15,7 @@ log.error = debug('libp2p:circuit:stop:error')
* @private
* @param {*} options
* @param {Connection} options.connection
* @param {*} options.request The CircuitRelay protobuf request (unencoded)
* @param {*} options.request - The CircuitRelay protobuf request (unencoded)
* @param {StreamHandler} options.streamHandler
* @returns {Promise<*>} Resolves a duplex iterable
*/
@ -42,10 +42,11 @@ module.exports.handleStop = function handleStop ({
/**
* Creates a STOP request
*
* @private
* @param {*} options
* @param {Connection} options.connection
* @param {*} options.request The CircuitRelay protobuf request (unencoded)
* @param {*} options.request - The CircuitRelay protobuf request (unencoded)
* @returns {Promise<*>} Resolves a duplex iterable
*/
module.exports.stop = async function stop ({

View File

@ -14,7 +14,7 @@ class StreamHandler {
*
* @param {object} options
* @param {*} options.stream - A duplex iterable
* @param {Number} options.maxLength - max bytes length of message
* @param {number} options.maxLength - max bytes length of message
*/
constructor ({ stream, maxLength = 4096 }) {
this.stream = stream
@ -25,6 +25,7 @@ class StreamHandler {
/**
* Read and decode message
*
* @async
* @returns {void}
*/
@ -44,7 +45,7 @@ class StreamHandler {
/**
* Encode and write array of buffers
*
* @param {*} msg An unencoded CircuitRelay protobuf message
* @param {*} msg - An unencoded CircuitRelay protobuf message
*/
write (msg) {
log('write message type %s', msg.type)
@ -54,7 +55,7 @@ class StreamHandler {
/**
* Return the handshake rest stream and invalidate handler
*
* @return {*} A duplex iterable
* @returns {*} A duplex iterable
*/
rest () {
this.shake.rest()

View File

@ -19,7 +19,7 @@ function writeResponse (streamHandler, status) {
/**
* Validate incomming HOP/STOP message
*
* @param {*} msg A CircuitRelay unencoded protobuf message
* @param {*} msg - A CircuitRelay unencoded protobuf message
* @param {StreamHandler} streamHandler
*/
function validateAddrs (msg, streamHandler) {

View File

@ -21,7 +21,7 @@ class Circuit {
/**
* Creates an instance of Circuit.
*
* @constructor
* @class
* @param {object} options
* @param {Libp2p} options.libp2p
* @param {Upgrader} options.upgrader
@ -122,11 +122,11 @@ class Circuit {
type: CircuitPB.Type.HOP,
srcPeer: {
id: this.peerId.toBytes(),
addrs: this._libp2p.multiaddrs.map(addr => addr.buffer)
addrs: this._libp2p.multiaddrs.map(addr => addr.bytes)
},
dstPeer: {
id: destinationPeer.toBytes(),
addrs: [multiaddr(destinationAddr).buffer]
addrs: [multiaddr(destinationAddr).bytes]
}
}
})
@ -152,7 +152,7 @@ class Circuit {
*
* @param {any} options
* @param {Function} handler
* @return {listener}
* @returns {listener}
*/
createListener (options, handler) {
if (typeof options === 'function') {

View File

@ -19,7 +19,7 @@ module.exports = (circuit) => {
* Add swarm handler and listen for incoming connections
*
* @param {Multiaddr} addr
* @return {void}
* @returns {void}
*/
listener.listen = async (addr) => {
const addrString = String(addr).split('/p2p-circuit').find(a => a !== '')
@ -34,7 +34,7 @@ module.exports = (circuit) => {
/**
* TODO: Remove the peers from our topology
*
* @return {void}
* @returns {void}
*/
listener.close = () => {}
@ -44,15 +44,15 @@ module.exports = (circuit) => {
* NOTE: This method will grab the peers multiaddrs and expand them such that:
*
* a) If it's an existing /p2p-circuit address for a specific relay i.e.
* `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit` this method will expand the
* address to `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit/ipfs/QmPeer` where
* `QmPeer` is this peers id
* `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit` this method will expand the
* address to `/ip4/0.0.0.0/tcp/0/ipfs/QmRelay/p2p-circuit/ipfs/QmPeer` where
* `QmPeer` is this peers id
* b) If it's not a /p2p-circuit address, it will encapsulate the address as a /p2p-circuit
* addr, such when dialing over a relay with this address, it will create the circuit using
* the encapsulated transport address. This is useful when for example, a peer should only
* be dialed over TCP rather than any other transport
* addr, such when dialing over a relay with this address, it will create the circuit using
* the encapsulated transport address. This is useful when for example, a peer should only
* be dialed over TCP rather than any other transport
*
* @return {Multiaddr[]}
* @returns {Multiaddr[]}
*/
listener.getAddrs = () => {
const addrs = []

View File

@ -12,7 +12,7 @@ const DefaultConfig = {
noAnnounce: []
},
connectionManager: {
minPeers: 25
minConnections: 25
},
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL

View File

@ -1,9 +1,12 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:connection-manager')
log.error = debug('libp2p:connection-manager:error')
const errcode = require('err-code')
const mergeOptions = require('merge-options')
const LatencyMonitor = require('./latency-monitor')
const debug = require('debug')('libp2p:connection-manager')
const retimer = require('retimer')
const { EventEmitter } = require('events')
@ -22,29 +25,33 @@ const defaultOptions = {
maxReceivedData: Infinity,
maxEventLoopDelay: Infinity,
pollInterval: 2000,
autoDialInterval: 10000,
movingAverageInterval: 60000,
defaultPeerValue: 1
}
/**
* Responsible for managing known connections.
*
* @fires ConnectionManager#peer:connect Emitted when a new peer is connected.
* @fires ConnectionManager#peer:disconnect Emitted when a peer is disconnected.
*/
class ConnectionManager extends EventEmitter {
/**
* @constructor
* @class
* @param {Libp2p} libp2p
* @param {object} options
* @param {Number} options.maxConnections The maximum number of connections allowed. Default=Infinity
* @param {Number} options.minConnections The minimum number of connections to avoid pruning. Default=0
* @param {Number} options.maxData The max data (in and out), per average interval to allow. Default=Infinity
* @param {Number} options.maxSentData The max outgoing data, per average interval to allow. Default=Infinity
* @param {Number} options.maxReceivedData The max incoming data, per average interval to allow.. Default=Infinity
* @param {Number} options.maxEventLoopDelay The upper limit the event loop can take to run. Default=Infinity
* @param {Number} options.pollInterval How often, in milliseconds, metrics and latency should be checked. Default=2000
* @param {Number} options.movingAverageInterval How often, in milliseconds, to compute averages. Default=60000
* @param {Number} options.defaultPeerValue The value of the peer. Default=1
* @param {number} options.maxConnections - The maximum number of connections allowed. Default=Infinity
* @param {number} options.minConnections - The minimum number of connections to avoid pruning. Default=0
* @param {number} options.maxData - The max data (in and out), per average interval to allow. Default=Infinity
* @param {number} options.maxSentData - The max outgoing data, per average interval to allow. Default=Infinity
* @param {number} options.maxReceivedData - The max incoming data, per average interval to allow.. Default=Infinity
* @param {number} options.maxEventLoopDelay - The upper limit the event loop can take to run. Default=Infinity
* @param {number} options.pollInterval - How often, in milliseconds, metrics and latency should be checked. Default=2000
* @param {number} options.movingAverageInterval - How often, in milliseconds, to compute averages. Default=60000
* @param {number} options.defaultPeerValue - The value of the peer. Default=1
* @param {boolean} options.autoDial - Should preemptively guarantee connections are above the low watermark. Default=true
* @param {number} options.autoDialInterval - How often, in milliseconds, it should preemptively guarantee connections are above the low watermark. Default=10000
*/
constructor (libp2p, options) {
super()
@ -57,24 +64,29 @@ class ConnectionManager extends EventEmitter {
throw errcode(new Error('Connection Manager maxConnections must be greater than minConnections'), ERR_INVALID_PARAMETERS)
}
debug('options: %j', this._options)
log('options: %j', this._options)
this._libp2p = libp2p
/**
* Map of peer identifiers to their peer value for pruning connections.
*
* @type {Map<string, number>}
*/
this._peerValues = new Map()
/**
* Map of connections per peer
*
* @type {Map<string, Array<conn>>}
*/
this.connections = new Map()
this._started = false
this._timer = null
this._autoDialTimeout = null
this._checkMetrics = this._checkMetrics.bind(this)
this._autoDial = this._autoDial.bind(this)
}
/**
@ -101,23 +113,31 @@ class ConnectionManager extends EventEmitter {
})
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
this._latencyMonitor.on('data', this._onLatencyMeasure)
debug('started')
this._started = true
log('started')
this._options.autoDial && this._autoDial()
}
/**
* Stops the Connection Manager
*
* @async
*/
async stop () {
this._autoDialTimeout && this._autoDialTimeout.clear()
this._timer && this._timer.clear()
this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
this._started = false
await this._close()
debug('stopped')
log('stopped')
}
/**
* Cleans up the connections
*
* @async
*/
async _close () {
@ -136,8 +156,9 @@ class ConnectionManager extends EventEmitter {
/**
* Sets the value of the given peer. Peers with lower values
* will be disconnected first.
*
* @param {PeerId} peerId
* @param {number} value A number between 0 and 1
* @param {number} value - A number between 0 and 1
*/
setPeerValue (peerId, value) {
if (value < 0 || value > 1) {
@ -152,22 +173,24 @@ class ConnectionManager extends EventEmitter {
/**
* Checks the libp2p metrics to determine if any values have exceeded
* the configured maximums.
*
* @private
*/
_checkMetrics () {
const movingAverages = this._libp2p.metrics.global.movingAverages
const received = movingAverages.dataReceived[this._options.movingAverageInterval].movingAverage()
this._checkLimit('maxReceivedData', received)
this._checkMaxLimit('maxReceivedData', received)
const sent = movingAverages.dataSent[this._options.movingAverageInterval].movingAverage()
this._checkLimit('maxSentData', sent)
this._checkMaxLimit('maxSentData', sent)
const total = received + sent
this._checkLimit('maxData', total)
debug('metrics update', total)
this._timer.reschedule(this._options.pollInterval)
this._checkMaxLimit('maxData', total)
log('metrics update', total)
this._timer = retimer(this._checkMetrics, this._options.pollInterval)
}
/**
* Tracks the incoming connection and check the connection limit
*
* @param {Connection} connection
*/
onConnect (connection) {
@ -188,11 +211,12 @@ class ConnectionManager extends EventEmitter {
this._peerValues.set(peerIdStr, this._options.defaultPeerValue)
}
this._checkLimit('maxConnections', this.size)
this._checkMaxLimit('maxConnections', this.size)
}
/**
* Removes the connection from tracking
*
* @param {Connection} connection
*/
onDisconnect (connection) {
@ -211,6 +235,7 @@ class ConnectionManager extends EventEmitter {
/**
* Get a connection with a peer.
*
* @param {PeerId} peerId
* @returns {Connection}
*/
@ -224,6 +249,7 @@ class ConnectionManager extends EventEmitter {
/**
* Get all open connections with a peer.
*
* @param {PeerId} peerId
* @returns {Array<Connection>}
*/
@ -244,42 +270,92 @@ class ConnectionManager extends EventEmitter {
/**
* If the event loop is slow, maybe close a connection
*
* @private
* @param {*} summary The LatencyMonitor summary
* @param {*} summary - The LatencyMonitor summary
*/
_onLatencyMeasure (summary) {
this._checkLimit('maxEventLoopDelay', summary.avgMs)
this._checkMaxLimit('maxEventLoopDelay', summary.avgMs)
}
/**
* If the `value` of `name` has exceeded its limit, maybe close a connection
*
* @private
* @param {string} name The name of the field to check limits for
* @param {number} value The current value of the field
* @param {string} name - The name of the field to check limits for
* @param {number} value - The current value of the field
*/
_checkLimit (name, value) {
_checkMaxLimit (name, value) {
const limit = this._options[name]
debug('checking limit of %s. current value: %d of %d', name, value, limit)
log('checking limit of %s. current value: %d of %d', name, value, limit)
if (value > limit) {
debug('%s: limit exceeded: %s, %d', this._peerId, name, value)
log('%s: limit exceeded: %s, %d', this._peerId, name, value)
this._maybeDisconnectOne()
}
}
/**
* Proactively tries to connect to known peers stored in the PeerStore.
* It will keep the number of connections below the upper limit and sort
* the peers to connect based on wether we know their keys and protocols.
*
* @async
* @private
*/
async _autoDial () {
const minConnections = this._options.minConnections
// Already has enough connections
if (this.size >= minConnections) {
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
return
}
// Sort peers on wether we know protocols of public keys for them
const peers = Array.from(this._libp2p.peerStore.peers.values())
.sort((a, b) => {
if (b.protocols && b.protocols.length && (!a.protocols || !a.protocols.length)) {
return 1
} else if (b.id.pubKey && !a.id.pubKey) {
return 1
}
return -1
})
for (let i = 0; i < peers.length && this.size < minConnections; i++) {
if (!this.get(peers[i].id)) {
log('connecting to a peerStore stored peer %s', peers[i].id.toB58String())
try {
await this._libp2p.dialer.connectToPeer(peers[i].id)
// Connection Manager was stopped
if (!this._started) {
return
}
} catch (err) {
log.error('could not connect to peerStore stored peer', err)
}
}
}
this._autoDialTimeout = retimer(this._autoDial, this._options.autoDialInterval)
}
/**
* If we have more connections than our maximum, close a connection
* to the lowest valued peer.
*
* @private
*/
_maybeDisconnectOne () {
if (this._options.minConnections < this.connections.size) {
const peerValues = Array.from(this._peerValues).sort(byPeerValue)
debug('%s: sorted peer values: %j', this._peerId, peerValues)
log('%s: sorted peer values: %j', this._peerId, peerValues)
const disconnectPeer = peerValues[0]
if (disconnectPeer) {
const peerId = disconnectPeer[0]
debug('%s: lowest value peer is %s', this._peerId, peerId)
debug('%s: closing a connection to %j', this._peerId, peerId)
log('%s: lowest value peer is %s', this._peerId, peerId)
log('%s: closing a connection to %j', this._peerId, peerId)
for (const connections of this.connections.values()) {
if (connections[0].remotePeer.toB58String() === peerId) {
connections[0].close()

View File

@ -12,11 +12,11 @@ const debug = require('debug')('latency-monitor:LatencyMonitor')
/**
* @typedef {Object} SummaryObject
* @property {Number} events How many events were called
* @property {Number} minMS What was the min time for a cb to be called
* @property {Number} maxMS What was the max time for a cb to be called
* @property {Number} avgMs What was the average time for a cb to be called
* @property {Number} lengthMs How long this interval was in ms
* @property {number} events How many events were called
* @property {number} minMS What was the min time for a cb to be called
* @property {number} maxMS What was the max time for a cb to be called
* @property {number} avgMs What was the average time for a cb to be called
* @property {number} lengthMs How long this interval was in ms
*/
/**
@ -37,11 +37,12 @@ const debug = require('debug')('latency-monitor:LatencyMonitor')
*/
class LatencyMonitor extends EventEmitter {
/**
* @param {Number} [latencyCheckIntervalMs=500] How often to add a latency check event (ms)
* @param {Number} [dataEmitIntervalMs=5000] How often to summarize latency check events. null or 0 disables event firing
* @param {function} [asyncTestFn] What cb-style async function to use
* @param {Number} [latencyRandomPercentage=5] What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events.
*/
* @param {object} [options]
* @param {number} [options.latencyCheckIntervalMs=500] - How often to add a latency check event (ms)
* @param {number} [options.dataEmitIntervalMs=5000] - How often to summarize latency check events. null or 0 disables event firing
* @param {Function} [options.asyncTestFn] - What cb-style async function to use
* @param {number} [options.latencyRandomPercentage=5] - What percent (+/-) of latencyCheckIntervalMs should we randomly use? This helps avoid alignment to other events.
*/
constructor ({ latencyCheckIntervalMs, dataEmitIntervalMs, asyncTestFn, latencyRandomPercentage } = {}) {
super()
const that = this
@ -106,9 +107,10 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Start internal timers
* @private
*/
* Start internal timers
*
* @private
*/
_startTimers () {
// Timer already started, ignore this
if (this._checkLatencyID) {
@ -124,9 +126,10 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Stop internal timers
* @private
*/
* Stop internal timers
*
* @private
*/
_stopTimers () {
if (this._checkLatencyID) {
clearTimeout(this._checkLatencyID)
@ -139,9 +142,10 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show
* @private
*/
* Emit summary only if there were events. It might not have any events if it was forced via a page hidden/show
*
* @private
*/
_emitSummary () {
const summary = this.getSummary()
if (summary.events > 0) {
@ -150,10 +154,11 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue,
* it will not count for this time period
* @returns {SummaryObject}
*/
* Calling this function will end the collection period. If a timing event was already fired and somewhere in the queue,
* it will not count for this time period
*
* @returns {SummaryObject}
*/
getSummary () {
// We might want to adjust for the number of expected events
// Example: first 1 event it comes back, then such a long blocker that the next emit check comes
@ -173,11 +178,11 @@ class LatencyMonitor extends EventEmitter {
}
/**
* Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found,
* it will simply report on event loop latency.
*
* @private
*/
* Randomly calls an async fn every roughly latencyCheckIntervalMs (plus some randomness). If no async fn is found,
* it will simply report on event loop latency.
*
* @private
*/
_checkLatency () {
const that = this
// Randomness is needed to avoid alignment by accident to regular things in the event loop

View File

@ -34,8 +34,8 @@ const debug = require('debug')('latency-monitor:VisibilityChangeEmitter')
*/
module.exports = class VisibilityChangeEmitter extends EventEmitter {
/**
* Creates a VisibilityChangeEmitter
*/
* Creates a VisibilityChangeEmitter
*/
constructor () {
super()
if (typeof document === 'undefined') {
@ -47,13 +47,14 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* document.hidden and document.visibilityChange are the two variables we need to check for;
* Since these variables are named differently in different browsers, this function sets
* the appropriate name based on the browser being used. Once executed, tha actual names of
* document.hidden and document.visibilityChange are found in this._hidden and this._visibilityChange
* respectively
* @private
*/
* document.hidden and document.visibilityChange are the two variables we need to check for;
* Since these variables are named differently in different browsers, this function sets
* the appropriate name based on the browser being used. Once executed, tha actual names of
* document.hidden and document.visibilityChange are found in this._hidden and this._visibilityChange
* respectively
*
* @private
*/
_initializeVisibilityVarNames () {
let hidden
let visibilityChange
@ -75,10 +76,11 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* Adds an event listener on the document that listens to changes in document.visibilityChange
* (or whatever name by which the visibilityChange variable is known in the browser)
* @private
*/
* Adds an event listener on the document that listens to changes in document.visibilityChange
* (or whatever name by which the visibilityChange variable is known in the browser)
*
* @private
*/
_addVisibilityChangeListener () {
if (typeof document.addEventListener === 'undefined' ||
typeof document[this._hidden] === 'undefined') {
@ -90,10 +92,11 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* The function returns ```true``` if the page is visible or ```false``` if the page is not visible and
* ```undefined``` if the page visibility API is not supported by the browser.
* @returns {Boolean|void} whether the page is now visible or not (undefined is unknown)
*/
* The function returns ```true``` if the page is visible or ```false``` if the page is not visible and
* ```undefined``` if the page visibility API is not supported by the browser.
*
* @returns {boolean | void} whether the page is now visible or not (undefined is unknown)
*/
isVisible () {
if (this._hidden === undefined || document[this._hidden] === undefined) {
return undefined
@ -103,12 +106,12 @@ module.exports = class VisibilityChangeEmitter extends EventEmitter {
}
/**
* The function that is called when document.visibilityChange has changed
* It emits an event called visibilityChange and sends the value of document.hidden as a
* parameter
*
* @private
*/
* The function that is called when document.visibilityChange has changed
* It emits an event called visibilityChange and sends the value of document.hidden as a
* parameter
*
* @private
*/
_handleVisibilityChange () {
const visible = !document[this._hidden]
debug(visible ? 'Page Visible' : 'Page Hidden')

View File

@ -20,9 +20,9 @@ module.exports = (node) => {
* Iterates over all content routers in series to find providers of the given key.
* Once a content router succeeds, iteration will stop.
*
* @param {CID} key The CID key of the content to find
* @param {CID} key - The CID key of the content to find
* @param {object} [options]
* @param {number} [options.timeout] How long the query should run
* @param {number} [options.timeout] - How long the query should run
* @param {number} [options.maxNumProviders] - maximum number of providers to find
* @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>}
*/
@ -51,7 +51,7 @@ module.exports = (node) => {
* Iterates over all content routers in parallel to notify it is
* a provider of the given key.
*
* @param {CID} key The CID key of the content to find
* @param {CID} key - The CID key of the content to find
* @returns {Promise<void>}
*/
async provide (key) { // eslint-disable-line require-await
@ -64,8 +64,9 @@ module.exports = (node) => {
/**
* Store the given key/value pair in the DHT.
* @param {Buffer} key
* @param {Buffer} value
*
* @param {Uint8Array} key
* @param {Uint8Array} value
* @param {Object} [options] - put options
* @param {number} [options.minPeers] - minimum number of peers required to successfully put
* @returns {Promise<void>}
@ -81,10 +82,11 @@ module.exports = (node) => {
/**
* Get the value to the given key.
* Times out after 1 minute by default.
* @param {Buffer} key
*
* @param {Uint8Array} key
* @param {Object} [options] - get options
* @param {number} [options.timeout] - optional timeout (default: 60000)
* @returns {Promise<{from: PeerId, val: Buffer}>}
* @returns {Promise<{from: PeerId, val: Uint8Array}>}
*/
async get (key, options) { // eslint-disable-line require-await
if (!node.isStarted() || !dht.isStarted) {
@ -96,11 +98,12 @@ module.exports = (node) => {
/**
* Get the `n` values to the given key without sorting.
* @param {Buffer} key
*
* @param {Uint8Array} key
* @param {number} nVals
* @param {Object} [options] - get options
* @param {number} [options.timeout] - optional timeout (default: 60000)
* @returns {Promise<Array<{from: PeerId, val: Buffer}>>}
* @returns {Promise<Array<{from: PeerId, val: Uint8Array}>>}
*/
async getMany (key, nVals, options) { // eslint-disable-line require-await
if (!node.isStarted() || !dht.isStarted) {

View File

@ -16,6 +16,7 @@ class DialRequest {
* from `dialer.getTokens`. Once a DialRequest is created, it can be
* started using `DialRequest.run(options)`. Once a single dial has succeeded,
* all other dials in the request will be cancelled.
*
* @param {object} options
* @param {Multiaddr[]} options.addrs
* @param {function(Multiaddr):Promise<Connection>} options.dialAction
@ -34,7 +35,7 @@ class DialRequest {
/**
* @async
* @param {object} options
* @param {AbortSignal} options.signal An AbortController signal
* @param {AbortSignal} options.signal - An AbortController signal
* @returns {Connection}
*/
async run (options) {

View File

@ -20,12 +20,13 @@ const {
class Dialer {
/**
* @constructor
* @class
* @param {object} options
* @param {TransportManager} options.transportManager
* @param {Peerstore} peerStore
* @param {number} options.concurrency Number of max concurrent dials. Defaults to `MAX_PARALLEL_DIALS`
* @param {number} options.timeout How long a dial attempt is allowed to take. Defaults to `DIAL_TIMEOUT`
* @param {Peerstore} options.peerStore
* @param {number} [options.concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
* @param {number} [options.perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
* @param {number} [options.timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
*/
constructor ({
transportManager,
@ -62,9 +63,9 @@ class Dialer {
* The dial to the first address that is successfully able to upgrade a connection
* will be used.
*
* @param {PeerId|Multiaddr|string} peer The peer to dial
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @param {AbortSignal} [options.signal] - An AbortController signal
* @returns {Promise<Connection>}
*/
async connectToPeer (peer, options = {}) {
@ -101,8 +102,9 @@ class Dialer {
* Creates a DialTarget. The DialTarget is used to create and track
* the DialRequest to a given peer.
* If a multiaddr is received it should be the first address attempted.
*
* @private
* @param {PeerId|Multiaddr|string} peer A PeerId or Multiaddr
* @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr
* @returns {DialTarget}
*/
_createDialTarget (peer) {
@ -112,7 +114,7 @@ class Dialer {
this.peerStore.addressBook.add(id, multiaddrs)
}
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id)
let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
// If received a multiaddr to dial, it should be the first to use
// But, if we know other multiaddrs for the peer, we should try them too.
@ -137,10 +139,11 @@ class Dialer {
/**
* Creates a PendingDial that wraps the underlying DialRequest
*
* @private
* @param {DialTarget} dialTarget
* @param {object} [options]
* @param {AbortSignal} [options.signal] An AbortController signal
* @param {AbortSignal} [options.signal] - An AbortController signal
* @returns {PendingDial}
*/
_createPendingDial (dialTarget, options) {

View File

@ -2,13 +2,15 @@
exports.messages = {
NOT_STARTED_YET: 'The libp2p node is not started yet',
DHT_DISABLED: 'DHT is not available'
DHT_DISABLED: 'DHT is not available',
CONN_ENCRYPTION_REQUIRED: 'At least one connection encryption module is required'
}
exports.codes = {
DHT_DISABLED: 'ERR_DHT_DISABLED',
PUBSUB_NOT_STARTED: 'ERR_PUBSUB_NOT_STARTED',
DHT_NOT_STARTED: 'ERR_DHT_NOT_STARTED',
CONN_ENCRYPTION_REQUIRED: 'ERR_CONN_ENCRYPTION_REQUIRED',
ERR_CONNECTION_ENDED: 'ERR_CONNECTION_ENDED',
ERR_CONNECTION_FAILED: 'ERR_CONNECTION_FAILED',
ERR_NODE_NOT_STARTED: 'ERR_NODE_NOT_STARTED',
@ -27,5 +29,6 @@ exports.codes = {
ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE',
ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED',
ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL',
ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR'
ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR',
ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID'
}

View File

@ -9,8 +9,8 @@ const { codes } = require('./errors')
/**
* Converts the given `peer` to a `Peer` object.
* If a multiaddr is received, the addressBook is updated.
*
* @param {PeerId|Multiaddr|string} peer
* @param {PeerStore} peerStore
* @returns {{ id: PeerId, multiaddrs: Array<Multiaddr> }}
*/
function getPeer (peer) {

View File

@ -1,6 +1,8 @@
'use strict'
const libp2pVersion = require('../../package.json').version
module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
module.exports.AGENT_VERSION = 'js-libp2p/0.1.0'
module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}`
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'

View File

@ -1,11 +1,15 @@
'use strict'
const { Buffer } = require('buffer')
const debug = require('debug')
const log = debug('libp2p:identify')
log.error = debug('libp2p:identify:error')
const errCode = require('err-code')
const pb = require('it-protocol-buffers')
const lp = require('it-length-prefixed')
const pipe = require('it-pipe')
const { collect, take, consume } = require('streaming-iterables')
const uint8ArrayFromString = require('uint8arrays/from-string')
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
@ -13,8 +17,8 @@ const { toBuffer } = require('it-buffer')
const Message = require('./message')
const log = debug('libp2p:identify')
log.error = debug('libp2p:identify:error')
const Envelope = require('../record/envelope')
const PeerRecord = require('../record/peer-record')
const {
MULTICODEC_IDENTIFY,
@ -23,13 +27,13 @@ const {
PROTOCOL_VERSION
} = require('./consts')
const errCode = require('err-code')
const { codes } = require('../errors')
class IdentifyService {
/**
* Takes the `addr` and converts it to a Multiaddr if possible
* @param {Buffer|String} addr
*
* @param {Uint8Array | string} addr
* @returns {Multiaddr|null}
*/
static getCleanMultiaddr (addr) {
@ -44,10 +48,10 @@ class IdentifyService {
}
/**
* @constructor
* @class
* @param {object} options
* @param {Libp2p} options.libp2p
* @param {Map<string, handler>} options.protocols A reference to the protocols we support
* @param {Map<string, handler>} options.protocols - A reference to the protocols we support
*/
constructor ({ libp2p, protocols }) {
/**
@ -83,18 +87,24 @@ class IdentifyService {
/**
* Send an Identify Push update to the list of connections
*
* @param {Array<Connection>} connections
* @returns {Promise<void>}
*/
push (connections) {
async push (connections) {
const signedPeerRecord = await this._getSelfPeerRecord()
const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes)
const protocols = Array.from(this._protocols.keys())
const pushes = connections.map(async connection => {
try {
const { stream } = await connection.newStream(MULTICODEC_IDENTIFY_PUSH)
await pipe(
[{
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer),
protocols: Array.from(this._protocols.keys())
listenAddrs,
signedPeerRecord,
protocols
}],
pb.encode(Message),
stream,
@ -111,6 +121,7 @@ class IdentifyService {
/**
* Calls `push` for all peers in the `peerStore` that are connected
*
* @param {PeerStore} peerStore
*/
pushToPeerStore (peerStore) {
@ -160,7 +171,8 @@ class IdentifyService {
publicKey,
listenAddrs,
protocols,
observedAddr
observedAddr,
signedPeerRecord
} = message
const id = await PeerId.createFromPubKey(publicKey)
@ -172,9 +184,25 @@ class IdentifyService {
// Get the observedAddr if there is one
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)
// Update peers data in PeerStore
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
try {
const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN)
if (this.peerStore.addressBook.consumePeerRecord(envelope)) {
this.peerStore.protoBook.set(id, protocols)
return
}
} catch (err) {
log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
}
// LEGACY: Update peers data in PeerStore
try {
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
} catch (err) {
log.error('received invalid addrs', err)
}
this.peerStore.protoBook.set(id, protocols)
this.peerStore.metadataBook.set(id, 'AgentVersion', uint8ArrayFromString(message.agentVersion))
// TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr)
@ -184,7 +212,7 @@ class IdentifyService {
* A handler to register with Libp2p to process identify messages.
*
* @param {object} options
* @param {String} options.protocol
* @param {string} options.protocol
* @param {*} options.stream
* @param {Connection} options.connection
* @returns {Promise<void>}
@ -201,76 +229,124 @@ class IdentifyService {
}
/**
* Sends the `Identify` response to the requesting peer over the
* given `connection`
* Sends the `Identify` response with the Signed Peer Record
* to the requesting peer over the given `connection`
*
* @private
* @param {object} options
* @param {*} options.stream
* @param {Connection} options.connection
*/
_handleIdentify ({ connection, stream }) {
let publicKey = Buffer.alloc(0)
async _handleIdentify ({ connection, stream }) {
let publicKey = new Uint8Array(0)
if (this.peerId.pubKey) {
publicKey = this.peerId.pubKey.bytes
}
const signedPeerRecord = await this._getSelfPeerRecord()
const message = Message.encode({
protocolVersion: PROTOCOL_VERSION,
agentVersion: AGENT_VERSION,
publicKey,
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.buffer),
observedAddr: connection.remoteAddr.buffer,
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes),
signedPeerRecord,
observedAddr: connection.remoteAddr.bytes,
protocols: Array.from(this._protocols.keys())
})
pipe(
[message],
lp.encode(),
stream,
consume
)
try {
await pipe(
[message],
lp.encode(),
stream,
consume
)
} catch (err) {
log.error('could not respond to identify request', err)
}
}
/**
* Reads the Identify Push message from the given `connection`
*
* @private
* @param {object} options
* @param {*} options.stream
* @param {Connection} options.connection
*/
async _handlePush ({ connection, stream }) {
const [data] = await pipe(
[],
stream,
lp.decode(),
take(1),
toBuffer,
collect
)
let message
try {
const [data] = await pipe(
[],
stream,
lp.decode(),
take(1),
toBuffer,
collect
)
message = Message.decode(data)
} catch (err) {
return log.error('received invalid message', err)
}
// Update peers data in PeerStore
const id = connection.remotePeer
try {
const envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN)
if (this.peerStore.addressBook.consumePeerRecord(envelope)) {
this.peerStore.protoBook.set(id, message.protocols)
return
}
} catch (err) {
log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
}
// LEGACY: Update peers data in PeerStore
try {
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr)))
} catch (err) {
return log.error('received invalid listen addrs', err)
log.error('received invalid addrs', err)
}
// Update the protocols
this.peerStore.protoBook.set(id, message.protocols)
}
/**
* Get self signed peer record raw envelope.
*
* @returns {Uint8Array}
*/
async _getSelfPeerRecord () {
const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId)
// TODO: support invalidation when dynamic multiaddrs are supported
if (selfSignedPeerRecord) {
return selfSignedPeerRecord
}
try {
const peerRecord = new PeerRecord({
peerId: this.peerId,
multiaddrs: this._libp2p.multiaddrs
})
const envelope = await Envelope.seal(peerRecord, this.peerId)
this.peerStore.addressBook.consumePeerRecord(envelope)
return this.peerStore.addressBook.getRawEnvelope(this.peerId)
} catch (err) {
log.error('failed to get self peer record')
}
return null
}
}
module.exports.IdentifyService = IdentifyService
/**
* The protocols the IdentifyService supports
*
* @property multicodecs
*/
module.exports.multicodecs = {

View File

@ -24,6 +24,11 @@ message Identify {
optional bytes observedAddr = 4;
repeated string protocols = 3;
// signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,
// signed by the sending node. It contains the same addresses as the listenAddrs field, but
// in a form that lets us share authenticated addrs with other peers.
optional bytes signedPeerRecord = 8;
}
`

View File

@ -6,14 +6,14 @@ const globalThis = require('ipfs-utils/src/globalthis')
const log = debug('libp2p')
log.error = debug('libp2p:error')
const errCode = require('err-code')
const PeerId = require('peer-id')
const peerRouting = require('./peer-routing')
const contentRouting = require('./content-routing')
const pubsub = require('./pubsub')
const getPeer = require('./get-peer')
const { validate: validateConfig } = require('./config')
const { codes } = require('./errors')
const { codes, messages } = require('./errors')
const AddressManager = require('./address-manager')
const ConnectionManager = require('./connection-manager')
@ -24,6 +24,7 @@ const Metrics = require('./metrics')
const TransportManager = require('./transport-manager')
const Upgrader = require('./upgrader')
const PeerStore = require('./peer-store')
const PubsubAdapter = require('./pubsub-adapter')
const PersistentPeerStore = require('./peer-store/persistent')
const Registrar = require('./registrar')
const ping = require('./ping')
@ -34,8 +35,6 @@ const {
/**
* @fires Libp2p#error Emitted when an error occurs
* @fires Libp2p#peer:connect Emitted when a peer is connected to this node
* @fires Libp2p#peer:disconnect Emitted when a peer disconnects from this node
* @fires Libp2p#peer:discovery Emitted when a peer is discovered
*/
class Libp2p extends EventEmitter {
@ -50,10 +49,11 @@ class Libp2p extends EventEmitter {
this.peerStore = (this.datastore && this._options.peerStore.persistence)
? new PersistentPeerStore({
peerId: this.peerId,
datastore: this.datastore,
...this._options.peerStore
})
: new PeerStore()
: new PeerStore({ peerId: this.peerId })
// Addresses {listen, announce, noAnnounce}
this.addresses = this._options.addresses
@ -65,7 +65,13 @@ class Libp2p extends EventEmitter {
this._discovery = new Map() // Discovery service instances/references
// Create the Connection Manager
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
if (this._options.connectionManager.minPeers) { // Remove in 0.29
this._options.connectionManager.minConnections = this._options.connectionManager.minPeers
}
this.connectionManager = new ConnectionManager(this, {
autoDial: this._config.peerDiscovery.autoDial,
...this._options.connectionManager
})
// Create Metrics
if (this._options.metrics.enabled) {
@ -76,7 +82,7 @@ class Libp2p extends EventEmitter {
}
// Create keychain
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
if (this._options.keychain && this._options.keychain.datastore) {
log('creating keychain')
const keychainOpts = Keychain.generateOptions()
@ -115,12 +121,13 @@ class Libp2p extends EventEmitter {
this.registrar.handle = this.handle
// Attach crypto channels
if (this._modules.connEncryption) {
const cryptos = this._modules.connEncryption
cryptos.forEach((crypto) => {
this.upgrader.cryptos.set(crypto.protocol, crypto)
})
if (!this._modules.connEncryption || !this._modules.connEncryption.length) {
throw errCode(new Error(messages.CONN_ENCRYPTION_REQUIRED), codes.CONN_ENCRYPTION_REQUIRED)
}
const cryptos = this._modules.connEncryption
cryptos.forEach((crypto) => {
this.upgrader.cryptos.set(crypto.protocol, crypto)
})
this.dialer = new Dialer({
transportManager: this.transportManager,
@ -166,6 +173,7 @@ class Libp2p extends EventEmitter {
if (this._modules.dht) {
const DHT = this._modules.dht
this._dht = new DHT({
libp2p: this,
dialer: this.dialer,
peerId: this.peerId,
peerStore: this.peerStore,
@ -175,9 +183,11 @@ class Libp2p extends EventEmitter {
})
}
// start pubsub
// Create pubsub if provided
if (this._modules.pubsub) {
this.pubsub = pubsub(this, this._modules.pubsub, this._config.pubsub)
const Pubsub = this._modules.pubsub
// using pubsub adapter with *DEPRECATED* handlers functionality
this.pubsub = PubsubAdapter(Pubsub, this, this._config.pubsub)
}
// Attach remaining APIs
@ -194,6 +204,7 @@ class Libp2p extends EventEmitter {
/**
* Overrides EventEmitter.emit to conditionally emit errors
* if there is a handler. If not, errors will be logged.
*
* @param {string} eventName
* @param {...any} args
* @returns {void}
@ -228,6 +239,7 @@ class Libp2p extends EventEmitter {
/**
* Stop the libp2p node by closing its listeners and open connections
*
* @async
* @returns {void}
*/
@ -269,6 +281,7 @@ class Libp2p extends EventEmitter {
/**
* Load keychain keys from the datastore.
* Imports the private key as 'self', if needed.
*
* @async
* @returns {void}
*/
@ -287,6 +300,7 @@ class Libp2p extends EventEmitter {
/**
* Gets a Map of the current connections. The keys are the stringified
* `PeerId` of the peer. The value is an array of Connections to that peer.
*
* @returns {Map<string, Connection[]>}
*/
get connections () {
@ -296,7 +310,8 @@ class Libp2p extends EventEmitter {
/**
* Dials to the provided peer. If successful, the known metadata of the
* peer will be added to the nodes `peerStore`
* @param {PeerId|Multiaddr|string} peer The peer to dial
*
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {object} options
* @param {AbortSignal} [options.signal]
* @returns {Promise<Connection>}
@ -309,15 +324,16 @@ class Libp2p extends EventEmitter {
* Dials to the provided peer and handshakes with the given protocol.
* If successful, the known metadata of the peer will be added to the nodes `peerStore`,
* and the `Connection` will be returned
*
* @async
* @param {PeerId|Multiaddr|string} peer The peer to dial
* @param {PeerId|Multiaddr|string} peer - The peer to dial
* @param {string[]|string} protocols
* @param {object} options
* @param {AbortSignal} [options.signal]
* @returns {Promise<Connection|*>}
*/
async dialProtocol (peer, protocols, options) {
const { id, multiaddrs } = getPeer(peer, this.peerStore)
const { id, multiaddrs } = getPeer(peer)
let connection = this.connectionManager.get(id)
if (!connection) {
@ -338,7 +354,8 @@ class Libp2p extends EventEmitter {
* Get peer advertising multiaddrs by concating the addresses used
* by transports to listen with the announce addresses.
* Duplicated addresses and noAnnounce addresses are filtered out.
* @return {Array<Multiaddr>}
*
* @returns {Array<Multiaddr>}
*/
get multiaddrs () {
// Filter noAnnounce multiaddrs
@ -364,7 +381,8 @@ class Libp2p extends EventEmitter {
/**
* Disconnects all connections to the given `peer`
* @param {PeerId|multiaddr|string} peer the peer to close connections to
*
* @param {PeerId|multiaddr|string} peer - the peer to close connections to
* @returns {Promise<void>}
*/
async hangUp (peer) {
@ -385,17 +403,24 @@ class Libp2p extends EventEmitter {
/**
* Pings the given peer in order to obtain the operation latency.
* @param {PeerId|Multiaddr|string} peer The peer to ping
*
* @param {PeerId|Multiaddr|string} peer - The peer to ping
* @returns {Promise<number>}
*/
ping (peer) {
const { id } = getPeer(peer)
const { id, multiaddrs } = getPeer(peer)
// If received multiaddr, ping it
if (multiaddrs) {
return ping(this, multiaddrs[0])
}
return ping(this, id)
}
/**
* Registers the `handler` for each protocol
*
* @param {string[]|string} protocols
* @param {function({ connection:*, stream:*, protocol:string })} handler
*/
@ -414,6 +439,7 @@ class Libp2p extends EventEmitter {
/**
* Removes the handler for each protocol. The protocol
* will no longer be supported on streams.
*
* @param {string[]|string} protocols
*/
unhandle (protocols) {
@ -454,24 +480,25 @@ class Libp2p extends EventEmitter {
/**
* Called when libp2p has started and before it returns
*
* @private
*/
async _onDidStart () {
this._isStarted = true
this.connectionManager.start()
this.peerStore.on('peer', peerId => {
this.emit('peer:discovery', peerId)
this._maybeConnect(peerId)
})
// Once we start, emit and dial any peers we may have already discovered
// Once we start, emit any peers we may have already discovered
// TODO: this should be removed, as we already discovered these peers in the past
for (const peer of this.peerStore.peers.values()) {
this.emit('peer:discovery', peer.id)
this._maybeConnect(peer.id)
}
this.connectionManager.start()
// Peer discovery
await this._setupPeerDiscovery()
}
@ -479,6 +506,7 @@ class Libp2p extends EventEmitter {
/**
* Called whenever peer discovery services emit `peer` events.
* Known peers may be emitted.
*
* @private
* @param {{ id: PeerId, multiaddrs: Array<Multiaddr>, protocols: Array<string> }} peer
*/
@ -495,15 +523,16 @@ class Libp2p extends EventEmitter {
/**
* Will dial to the given `peerId` if the current number of
* connected peers is less than the configured `ConnectionManager`
* minPeers.
* minConnections.
*
* @private
* @param {PeerId} peerId
*/
async _maybeConnect (peerId) {
// If auto dialing is on and we have no connection to the peer, check if we should dial
if (this._config.peerDiscovery.autoDial === true && !this.connectionManager.get(peerId)) {
const minPeers = this._options.connectionManager.minPeers || 0
if (minPeers > this.connectionManager.size) {
const minConnections = this._options.connectionManager.minConnections || 0
if (minConnections > this.connectionManager.size) {
log('connecting to discovered peer %s', peerId.toB58String())
try {
await this.dialer.connectToPeer(peerId)
@ -569,7 +598,8 @@ class Libp2p extends EventEmitter {
/**
* Like `new Libp2p(options)` except it will create a `PeerId`
* instance if one is not provided in options.
* @param {object} options Libp2p configuration options
*
* @param {object} options - Libp2p configuration options
* @returns {Libp2p}
*/
Libp2p.create = async function create (options = {}) {

View File

@ -43,7 +43,7 @@ async function encrypt (localId, conn, remoteId) {
throw new InvalidCryptoExchangeError('Remote did not provide its public key')
}
if (remoteId && !peerId.isEqual(remoteId)) {
if (remoteId && !peerId.equals(remoteId)) {
throw new UnexpectedPeerError()
}

View File

@ -5,6 +5,8 @@ require('node-forge/lib/pbe')
const forge = require('node-forge/lib/forge')
const { certificateForKey, findAsync } = require('./util')
const errcode = require('err-code')
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
/**
* Cryptographic Message Syntax (aka PKCS #7)
@ -32,15 +34,15 @@ class CMS {
/**
* Creates some protected data.
*
* The output Buffer contains the PKCS #7 message in DER.
* The output Uint8Array contains the PKCS #7 message in DER.
*
* @param {string} name - The local key name.
* @param {Buffer} plain - The data to encrypt.
* @param {Uint8Array} plain - The data to encrypt.
* @returns {undefined}
*/
async encrypt (name, plain) {
if (!Buffer.isBuffer(plain)) {
throw errcode(new Error('Plain data must be a Buffer'), 'ERR_INVALID_PARAMS')
if (!(plain instanceof Uint8Array)) {
throw errcode(new Error('Plain data must be a Uint8Array'), 'ERR_INVALID_PARAMS')
}
const key = await this.keychain.findKeyByName(name)
@ -56,7 +58,7 @@ class CMS {
// convert message to DER
const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
return Buffer.from(der, 'binary')
return uint8ArrayFromString(der, 'ascii')
}
/**
@ -65,17 +67,17 @@ class CMS {
* The keychain must contain one of the keys used to encrypt the data. If none of the keys
* exists, an Error is returned with the property 'missingKeys'. It is array of key ids.
*
* @param {Buffer} cmsData - The CMS encrypted data to decrypt.
* @param {Uint8Array} cmsData - The CMS encrypted data to decrypt.
* @returns {undefined}
*/
async decrypt (cmsData) {
if (!Buffer.isBuffer(cmsData)) {
if (!(cmsData instanceof Uint8Array)) {
throw errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS')
}
let cms
try {
const buf = forge.util.createBuffer(cmsData.toString('binary'))
const buf = forge.util.createBuffer(uint8ArrayToString(cmsData, 'ascii'))
const obj = forge.asn1.fromDer(buf)
cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) {
@ -115,7 +117,7 @@ class CMS {
const pem = await this.keychain._getPrivateKey(key.name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
cms.decrypt(r.recipient, privateKey)
return Buffer.from(cms.content.getBytes(), 'binary')
return uint8ArrayFromString(cms.content.getBytes(), 'ascii')
}
}

View File

@ -7,6 +7,11 @@ const crypto = require('libp2p-crypto')
const DS = require('interface-datastore')
const CMS = require('./cms')
const errcode = require('err-code')
const { Number } = require('ipfs-utils/src/globalthis')
const uint8ArrayToString = require('uint8arrays/to-string')
const uint8ArrayFromString = require('uint8arrays/from-string')
require('node-forge/lib/sha512')
const keyPrefix = '/pkcs8/'
const infoPrefix = '/info/'
@ -107,7 +112,7 @@ class Keychain {
this.opts = mergeOptions(defaultOptions, options)
// Enforce NIST SP 800-132
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
throw new Error('passPhrase must be least 20 characters')
}
if (this.opts.dek.keyLength < NIST.minKeyLength) {
@ -120,13 +125,13 @@ class Keychain {
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
}
// Create the derived encrypting key
const dek = crypto.pbkdf2(
const dek = this.opts.passPhrase ? crypto.pbkdf2(
this.opts.passPhrase,
this.opts.dek.salt,
this.opts.dek.iterationCount,
this.opts.dek.keyLength,
this.opts.dek.hash)
this.opts.dek.hash) : ''
Object.defineProperty(this, '_', { value: () => dek })
}
@ -152,7 +157,7 @@ class Keychain {
static generateOptions () {
const options = Object.assign({}, defaultOptions)
const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding
options.dek.salt = crypto.randomBytes(saltLength).toString('base64')
options.dek.salt = uint8ArrayToString(crypto.randomBytes(saltLength), 'base64')
return options
}
@ -171,8 +176,8 @@ class Keychain {
*
* @param {string} name - The local key name; cannot already exist.
* @param {string} type - One of the key types; 'rsa'.
* @param {int} size - The key size in bits.
* @returns {KeyInfo}
* @param {int} [size] - The key size in bits. Used for rsa keys only.
* @returns {KeyInfo}
*/
async createKey (name, type, size) {
const self = this
@ -185,17 +190,13 @@ class Keychain {
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
}
if (!Number.isSafeInteger(size)) {
return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
}
const dsname = DsName(name)
const exists = await self.store.has(dsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
switch (type.toLowerCase()) {
case 'rsa':
if (size < 2048) {
if (!Number.isSafeInteger(size) || size < 2048) {
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
}
break
@ -213,8 +214,8 @@ class Keychain {
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
batch.put(dsname, uint8ArrayFromString(pem))
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
await batch.commit()
} catch (err) {
@ -227,7 +228,7 @@ class Keychain {
/**
* List all the keys.
*
* @returns {KeyInfo[]}
* @returns {KeyInfo[]}
*/
async listKeys () {
const self = this
@ -237,7 +238,7 @@ class Keychain {
const info = []
for await (const value of self.store.query(query)) {
info.push(JSON.parse(value.value))
info.push(JSON.parse(uint8ArrayToString(value.value)))
}
return info
@ -247,7 +248,7 @@ class Keychain {
* Find a key by it's id.
*
* @param {string} id - The universally unique key identifier.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async findKeyById (id) {
try {
@ -262,7 +263,7 @@ class Keychain {
* Find a key by it's name.
*
* @param {string} name - The local key name.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async findKeyByName (name) {
if (!validateKeyName(name)) {
@ -272,7 +273,7 @@ class Keychain {
const dsname = DsInfoName(name)
try {
const res = await this.store.get(dsname)
return JSON.parse(res.toString())
return JSON.parse(uint8ArrayToString(res))
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
@ -282,7 +283,7 @@ class Keychain {
* Remove an existing key.
*
* @param {string} name - The local key name; must already exist.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async removeKey (name) {
const self = this
@ -303,7 +304,7 @@ class Keychain {
*
* @param {string} oldName - The old local key name; must already exist.
* @param {string} newName - The new local key name; must not already exist.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async renameKey (oldName, newName) {
const self = this
@ -322,15 +323,14 @@ class Keychain {
if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
try {
let res = await this.store.get(oldDsname)
const pem = res.toString()
res = await self.store.get(oldInfoName)
const pem = await self.store.get(oldDsname)
const res = await self.store.get(oldInfoName)
const keyInfo = JSON.parse(res.toString())
const keyInfo = JSON.parse(uint8ArrayToString(res))
keyInfo.name = newName
const batch = self.store.batch()
batch.put(newDsname, pem)
batch.put(newInfoName, JSON.stringify(keyInfo))
batch.put(newInfoName, uint8ArrayFromString(JSON.stringify(keyInfo)))
batch.delete(oldDsname)
batch.delete(oldInfoName)
await batch.commit()
@ -345,7 +345,7 @@ class Keychain {
*
* @param {string} name - The local key name; must already exist.
* @param {string} password - The password
* @returns {string}
* @returns {string}
*/
async exportKey (name, password) {
if (!validateKeyName(name)) {
@ -358,7 +358,7 @@ class Keychain {
const dsname = DsName(name)
try {
const res = await this.store.get(dsname)
const pem = res.toString()
const pem = uint8ArrayToString(res)
const privateKey = await crypto.keys.import(pem, this._())
return privateKey.export(password)
} catch (err) {
@ -372,7 +372,7 @@ class Keychain {
* @param {string} name - The local key name; must not already exist.
* @param {string} pem - The PEM encoded PKCS #8 string
* @param {string} password - The password.
* @returns {KeyInfo}
* @returns {KeyInfo}
*/
async importKey (name, pem, password) {
const self = this
@ -406,8 +406,8 @@ class Keychain {
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
batch.put(dsname, uint8ArrayFromString(pem))
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
await batch.commit()
return keyInfo
@ -435,8 +435,8 @@ class Keychain {
id: kid
}
const batch = self.store.batch()
batch.put(dsname, pem)
batch.put(DsInfoName(name), JSON.stringify(keyInfo))
batch.put(dsname, uint8ArrayFromString(pem))
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
await batch.commit()
return keyInfo
} catch (err) {
@ -448,7 +448,7 @@ class Keychain {
* Gets the private key as PEM encoded PKCS #8 string.
*
* @param {string} name
* @returns {string}
* @returns {string}
* @private
*/
async _getPrivateKey (name) {
@ -459,7 +459,7 @@ class Keychain {
try {
const dsname = DsName(name)
const res = await this.store.get(dsname)
return res.toString()
return uint8ArrayToString(res)
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}

View File

@ -8,13 +8,13 @@ exports = module.exports
/**
* Gets a self-signed X.509 certificate for the key.
*
* The output Buffer contains the PKCS #7 message in DER.
* The output Uint8Array contains the PKCS #7 message in DER.
*
* TODO: move to libp2p-crypto package
*
* @param {KeyInfo} key - The id and name of the key
* @param {RsaPrivateKey} privateKey - The naked key
* @returns {undefined}
* @returns {Uint8Array}
*/
exports.certificateForKey = (key, privateKey) => {
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
@ -77,7 +77,7 @@ exports.certificateForKey = (key, privateKey) => {
* resolve to either `true` or `false`.
*
* @param {Array} array
* @param {function(*)} asyncCompare An async function that returns a boolean
* @param {function(*)} asyncCompare - An async function that returns a boolean
*/
async function findAsync (array, asyncCompare) {
const promises = array.map(asyncCompare)

View File

@ -66,6 +66,7 @@ class Metrics {
/**
* Gets the global `Stats` object
*
* @returns {Stats}
*/
get global () {
@ -74,6 +75,7 @@ class Metrics {
/**
* Returns a list of `PeerId` strings currently being tracked
*
* @returns {Array<string>}
*/
get peers () {
@ -83,6 +85,7 @@ class Metrics {
/**
* Returns the `Stats` object for the given `PeerId` whether it
* is a live peer, or in the disconnected peer LRU cache.
*
* @param {PeerId} peerId
* @returns {Stats}
*/
@ -93,6 +96,7 @@ class Metrics {
/**
* Returns a list of all protocol strings currently being tracked.
*
* @returns {Array<string>}
*/
get protocols () {
@ -101,6 +105,7 @@ class Metrics {
/**
* Returns the `Stats` object for the given `protocol`.
*
* @param {string} protocol
* @returns {Stats}
*/
@ -112,6 +117,7 @@ class Metrics {
* Should be called when all connections to a given peer
* have closed. The `Stats` collection for the peer will
* be stopped and moved to an LRU for temporary retention.
*
* @param {PeerId} peerId
*/
onPeerDisconnected (peerId) {
@ -131,10 +137,10 @@ class Metrics {
*
* @private
* @param {object} params
* @param {PeerId} params.remotePeer Remote peer
* @param {string} [params.protocol] Protocol string the stream is running
* @param {string} params.direction One of ['in','out']
* @param {number} params.dataLength Size of the message
* @param {PeerId} params.remotePeer - Remote peer
* @param {string} [params.protocol] - Protocol string the stream is running
* @param {string} params.direction - One of ['in','out']
* @param {number} params.dataLength - Size of the message
* @returns {void}
*/
_onMessage ({ remotePeer, protocol, direction, dataLength }) {
@ -167,7 +173,8 @@ class Metrics {
* Replaces the `PeerId` string with the given `peerId`.
* If stats are already being tracked for the given `peerId`, the
* placeholder stats will be merged with the existing stats.
* @param {PeerId} placeholder A peerId string
*
* @param {PeerId} placeholder - A peerId string
* @param {PeerId} peerId
*/
updatePlaceholder (placeholder, peerId) {
@ -198,9 +205,9 @@ class Metrics {
* with the placeholder string returned from here, and the known `PeerId`.
*
* @param {Object} options
* @param {{ sink: function(*), source: function() }} options.stream A duplex iterable stream
* @param {PeerId} [options.peerId] The id of the remote peer that's connected
* @param {string} [options.protocol] The protocol the stream is running
* @param {{ sink: function(*), source: function() }} options.stream - A duplex iterable stream
* @param {PeerId} [options.remotePeer] - The id of the remote peer that's connected
* @param {string} [options.protocol] - The protocol the stream is running
* @returns {string} The peerId string or placeholder string
*/
trackStream ({ stream, remotePeer, protocol }) {
@ -215,7 +222,7 @@ class Metrics {
const _sink = stream.sink
stream.sink = source => {
pipe(
return pipe(
source,
tap(chunk => metrics._onMessage({
remotePeer,
@ -233,6 +240,7 @@ class Metrics {
/**
* Merges `other` into `target`. `target` will be modified
* and returned.
*
* @param {Stats} target
* @param {Stats} other
* @returns {Stats}

View File

@ -5,7 +5,7 @@ const LRU = require('hashlru')
/**
* Creates and returns a Least Recently Used Cache
*
* @param {Number} maxSize
* @param {number} maxSize
* @returns {LRUCache}
*/
module.exports = (maxSize) => {

View File

@ -128,11 +128,7 @@ class Stats extends EventEmitter {
* @returns {void}
*/
_resetComputeTimeout () {
if (this._timeout) {
this._timeout.reschedule(this._nextTimeout())
} else {
this._timeout = retimer(this._update, this._nextTimeout())
}
this._timeout = retimer(this._update, this._nextTimeout())
}
/**
@ -200,8 +196,8 @@ class Stats extends EventEmitter {
*
* @private
* @param {string} key
* @param {number} timeDiffMS Time in milliseconds
* @param {Timestamp} latestTime Time in ticks
* @param {number} timeDiffMS - Time in milliseconds
* @param {Timestamp} latestTime - Time in ticks
* @returns {void}
*/
_updateFrequencyFor (key, timeDiffMS, latestTime) {

View File

@ -15,9 +15,9 @@ module.exports = (node) => {
/**
* Iterates over all peer routers in series to find the given peer.
*
* @param {String} id The id of the peer to find
* @param {string} id - The id of the peer to find
* @param {object} [options]
* @param {number} [options.timeout] How long the query should run
* @param {number} [options.timeout] - How long the query should run
* @returns {Promise<{ id: PeerId, multiaddrs: Multiaddr[] }>}
*/
findPeer: async (id, options) => { // eslint-disable-line require-await

View File

@ -75,9 +75,9 @@ A `peerId.toB58String()` identifier mapping to a `Set` of protocol identifier st
#### Metadata Book
The `metadataBook` keeps track of the known metadata of a peer. Its metadata is stored in a key value fashion, where a key identifier (`string`) represents a metadata value (`Buffer`).
The `metadataBook` keeps track of the known metadata of a peer. Its metadata is stored in a key value fashion, where a key identifier (`string`) represents a metadata value (`Uint8Array`).
`Map<string, Map<string, Buffer>>`
`Map<string, Map<string, Uint8Array>>`
A `peerId.toB58String()` identifier mapping to the peer metadata Map.

View File

@ -9,10 +9,12 @@ const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const Book = require('./book')
const PeerRecord = require('../record/peer-record')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
const Envelope = require('../record/envelope')
/**
* The AddressBook is responsible for keeping the known multiaddrs
@ -21,14 +23,32 @@ const {
class AddressBook extends Book {
/**
* Address object
*
* @typedef {Object} Address
* @property {Multiaddr} multiaddr peer multiaddr.
* @property {boolean} isCertified obtained from a signed peer record.
*/
/**
* @constructor
* @param {PeerStore} peerStore
*/
* CertifiedRecord object
*
* @typedef {Object} CertifiedRecord
* @property {Uint8Array} raw raw envelope.
* @property {number} seqNumber seq counter.
*/
/**
* Entry object for the addressBook
*
* @typedef {Object} Entry
* @property {Array<Address>} addresses peer Addresses.
* @property {CertifiedRecord} record certified peer record.
*/
/**
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the AddressBook to emit:
@ -39,18 +59,116 @@ class AddressBook extends Book {
peerStore,
eventName: 'change:multiaddrs',
eventProperty: 'multiaddrs',
eventTransformer: (data) => data.map((address) => address.multiaddr)
eventTransformer: (data) => {
if (!data.addresses) {
return []
}
return data.addresses.map((address) => address.multiaddr)
}
})
/**
* Map known peers to their known Addresses.
* @type {Map<string, Array<Address>>}
* Map known peers to their known Address Entries.
*
* @type {Map<string, Array<Entry>>}
*/
this.data = new Map()
}
/**
* ConsumePeerRecord adds addresses from a signed peer record contained in a record envelope.
* This will return a boolean that indicates if the record was successfully processed and added
* into the AddressBook.
*
* @param {Envelope} envelope
* @returns {boolean}
*/
consumePeerRecord (envelope) {
let peerRecord
try {
peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
} catch (err) {
log.error('invalid peer record received')
return false
}
// Verify peerId
if (!peerRecord.peerId.equals(envelope.peerId)) {
log('signing key does not match PeerId in the PeerRecord')
return false
}
// ensure the record has multiaddrs
if (!peerRecord.multiaddrs || !peerRecord.multiaddrs.length) {
return false
}
const peerId = peerRecord.peerId
const id = peerId.toB58String()
const entry = this.data.get(id) || {}
const storedRecord = entry.record
// ensure seq is greater than, or equal to, the last received
if (storedRecord && storedRecord.seqNumber >= peerRecord.seqNumber) {
return false
}
const addresses = this._toAddresses(peerRecord.multiaddrs, true)
// Replace unsigned addresses by the new ones from the record
// TODO: Once we have ttls for the addresses, we should merge these in.
this._setData(peerId, {
addresses,
record: {
raw: envelope.marshal(),
seqNumber: peerRecord.seqNumber
}
})
log(`stored provided peer record for ${id}`)
return true
}
/**
* Get the raw Envelope for a peer. Returns
* undefined if no Envelope is found.
*
* @param {PeerId} peerId
* @returns {Uint8Array|undefined}
*/
getRawEnvelope (peerId) {
const entry = this.data.get(peerId.toB58String())
if (!entry || !entry.record || !entry.record.raw) {
return undefined
}
return entry.record.raw
}
/**
* Get an Envelope containing a PeerRecord for the given peer.
* Returns undefined if no record exists.
*
* @param {PeerId} peerId
* @returns {Promise<Envelope|void>}
*/
getPeerRecord (peerId) {
const raw = this.getRawEnvelope(peerId)
if (!raw) {
return undefined
}
return Envelope.createFromProtobuf(raw)
}
/**
* Set known multiaddrs of a provided peer.
* This will replace previously stored multiaddrs, if available.
* Replacing stored multiaddrs might result in losing obtained certified addresses.
* If you are not sure, it's recommended to use `add` instead.
*
* @override
* @param {PeerId} peerId
* @param {Array<Multiaddr>} multiaddrs
@ -64,7 +182,8 @@ class AddressBook extends Book {
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const rec = this.data.get(id)
const entry = this.data.get(id) || {}
const rec = entry.addresses
// Not replace multiaddrs
if (!addresses.length) {
@ -73,7 +192,7 @@ class AddressBook extends Book {
// Already knows the peer
if (rec && rec.length === addresses.length) {
const intersection = rec.filter((mi) => addresses.some((newMi) => mi.multiaddr.equals(newMi.multiaddr)))
const intersection = rec.filter((addr) => addresses.some((newAddr) => addr.multiaddr.equals(newAddr.multiaddr)))
// Are new addresses equal to the old ones?
// If yes, no changes needed!
@ -83,7 +202,10 @@ class AddressBook extends Book {
}
}
this._setData(peerId, addresses)
this._setData(peerId, {
addresses,
record: entry.record
})
log(`stored provided multiaddrs for ${id}`)
// Notify the existance of a new peer
@ -97,6 +219,7 @@ class AddressBook extends Book {
/**
* Add known addresses of a provided peer.
* If the peer is not known, it is set with the given addresses.
*
* @param {PeerId} peerId
* @param {Array<Multiaddr>} multiaddrs
* @returns {AddressBook}
@ -109,12 +232,14 @@ class AddressBook extends Book {
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const rec = this.data.get(id)
const entry = this.data.get(id) || {}
const rec = entry.addresses || []
// Add recorded uniquely to the new array (Union)
rec && rec.forEach((mi) => {
if (!addresses.find(r => r.multiaddr.equals(mi.multiaddr))) {
addresses.push(mi)
rec.forEach((addr) => {
if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) {
addresses.push(addr)
}
})
@ -125,25 +250,47 @@ class AddressBook extends Book {
return this
}
this._setData(peerId, addresses)
this._setData(peerId, {
addresses,
record: entry.record
})
log(`added provided multiaddrs for ${id}`)
// Notify the existance of a new peer
if (!rec) {
if (!entry.addresses) {
this._ps.emit('peer', peerId)
}
return this
}
/**
* Get the known data of a provided peer.
*
* @override
* @param {PeerId} peerId
* @returns {Array<data>}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const entry = this.data.get(peerId.toB58String())
return entry && entry.addresses ? [...entry.addresses] : undefined
}
/**
* Transforms received multiaddrs into Address.
*
* @private
* @param {Array<Multiaddr>} multiaddrs
* @param {boolean} [isCertified]
* @returns {Array<Address>}
*/
_toAddresses (multiaddrs) {
_toAddresses (multiaddrs, isCertified = false) {
if (!multiaddrs) {
log.error('multiaddrs must be provided to store data')
throw errcode(new Error('multiaddrs must be provided'), ERR_INVALID_PARAMETERS)
@ -158,7 +305,8 @@ class AddressBook extends Book {
}
addresses.push({
multiaddr: addr
multiaddr: addr,
isCertified
})
})
@ -168,21 +316,23 @@ class AddressBook extends Book {
/**
* Get the known multiaddrs for a given peer. All returned multiaddrs
* will include the encapsulated `PeerId` of the peer.
* Returns `undefined` if there are no known multiaddrs for the given peer.
*
* @param {PeerId} peerId
* @returns {Array<Multiaddr>}
* @returns {Array<Multiaddr>|undefined}
*/
getMultiaddrsForPeer (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const record = this.data.get(peerId.toB58String())
const entry = this.data.get(peerId.toB58String())
if (!record) {
if (!entry || !entry.addresses) {
return undefined
}
return record.map((address) => {
return entry.addresses.map((address) => {
const multiaddr = address.multiaddr
const idString = multiaddr.getPeerId()

View File

@ -14,12 +14,12 @@ const passthrough = data => data
*/
class Book {
/**
* @constructor
* @class
* @param {Object} properties
* @param {PeerStore} properties.peerStore PeerStore instance.
* @param {string} properties.eventName Name of the event to emit by the PeerStore.
* @param {string} properties.eventProperty Name of the property to emit by the PeerStore.
* @param {function} [properties.eventTransformer] Transformer function of the provided data for being emitted.
* @param {PeerStore} properties.peerStore - PeerStore instance.
* @param {string} properties.eventName - Name of the event to emit by the PeerStore.
* @param {string} properties.eventProperty - Name of the property to emit by the PeerStore.
* @param {Function} [properties.eventTransformer] - Transformer function of the provided data for being emitted.
*/
constructor ({ peerStore, eventName, eventProperty, eventTransformer = passthrough }) {
this._ps = peerStore
@ -29,6 +29,7 @@ class Book {
/**
* Map known peers to their data.
*
* @type {Map<string, Array<Data>}
*/
this.data = new Map()
@ -36,6 +37,7 @@ class Book {
/**
* Set known data of a provided peer.
*
* @param {PeerId} peerId
* @param {Array<Data>|Data} data
*/
@ -45,12 +47,13 @@ class Book {
/**
* Set data into the datastructure, persistence and emit it using the provided transformers.
*
* @private
* @param {PeerId} peerId peerId of the data to store
* @param {*} data data to store.
* @param {Object} [options] storing options.
* @param {boolean} [options.emit = true] emit the provided data.
* @return {void}
* @param {PeerId} peerId - peerId of the data to store
* @param {*} data - data to store.
* @param {Object} [options] - storing options.
* @param {boolean} [options.emit = true] - emit the provided data.
* @returns {void}
*/
_setData (peerId, data, { emit = true } = {}) {
const b58key = peerId.toB58String()
@ -64,6 +67,7 @@ class Book {
/**
* Emit data.
*
* @private
* @param {PeerId} peerId
* @param {*} data
@ -77,8 +81,10 @@ class Book {
/**
* Get the known data of a provided peer.
* Returns `undefined` if there is no available data for the given peer.
*
* @param {PeerId} peerId
* @returns {Array<Data>}
* @returns {Array<Data>|undefined}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
@ -92,6 +98,7 @@ class Book {
/**
* Deletes the provided peer from the book.
*
* @param {PeerId} peerId
* @returns {boolean}
*/

View File

@ -19,6 +19,7 @@ const {
/**
* Responsible for managing known peers, as well as their addresses, protocols and metadata.
*
* @fires PeerStore#peer Emitted when a new peer is added.
* @fires PeerStore#change:protocols Emitted when a known peer supports a different set of protocols.
* @fires PeerStore#change:multiaddrs Emitted when a known peer has a different set of multiaddrs.
@ -28,18 +29,24 @@ const {
class PeerStore extends EventEmitter {
/**
* Peer object
*
* @typedef {Object} Peer
* @property {PeerId} id peer's peer-id instance.
* @property {Array<Address>} addresses peer's addresses containing its multiaddrs and metadata.
* @property {Array<string>} protocols peer's supported protocols.
* @property {Map<string, Buffer>} metadata peer's metadata map.
*/
/**
* @constructor
* @param {object} options
* @param {PeerId} options.peerId
* @class
*/
constructor () {
constructor ({ peerId }) {
super()
this._peerId = peerId
/**
* AddressBook containing a map of peerIdStr to Address.
*/
@ -72,7 +79,8 @@ class PeerStore extends EventEmitter {
stop () {}
/**
* Get all the stored information of every peer.
* Get all the stored information of every peer known.
*
* @returns {Map<string, Peer>}
*/
get peers () {
@ -83,6 +91,9 @@ class PeerStore extends EventEmitter {
...this.metadataBook.data.keys()
])
// Remove self peer if present
this._peerId && storedPeers.delete(this._peerId.toB58String())
const peersData = new Map()
storedPeers.forEach((idStr) => {
peersData.set(idStr, this.get(PeerId.createFromCID(idStr)))
@ -93,6 +104,7 @@ class PeerStore extends EventEmitter {
/**
* Delete the information of the given peer in every book.
*
* @param {PeerId} peerId
* @returns {boolean} true if found and removed
*/
@ -107,6 +119,7 @@ class PeerStore extends EventEmitter {
/**
* Get the stored information of a given peer.
*
* @param {PeerId} peerId
* @returns {Peer}
*/

View File

@ -18,9 +18,9 @@ const {
*/
class KeyBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
super({
peerStore,
@ -31,6 +31,7 @@ class KeyBook extends Book {
/**
* Map known peers to their known Public Key.
*
* @type {Map<string, PeerId>}
*/
this.data = new Map()
@ -38,11 +39,12 @@ class KeyBook extends Book {
/**
* Set the Peer public key.
*
* @override
* @param {PeerId} peerId
* @param {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey} publicKey
* @return {KeyBook}
*/
* @returns {KeyBook}
*/
set (peerId, publicKey) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
@ -67,9 +69,10 @@ class KeyBook extends Book {
/**
* Get Public key of the given PeerId, if stored.
*
* @override
* @param {PeerId} peerId
* @return {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey}
* @returns {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {

View File

@ -4,8 +4,7 @@ const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:proto-book')
log.error = debug('libp2p:peer-store:proto-book:error')
const { Buffer } = require('buffer')
const uint8ArrayEquals = require('uint8arrays/equals')
const PeerId = require('peer-id')
@ -18,13 +17,14 @@ const {
/**
* The MetadataBook is responsible for keeping the known supported
* protocols of a peer.
*
* @fires MetadataBook#change:metadata
*/
class MetadataBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the MetadataBook to emit:
@ -38,17 +38,19 @@ class MetadataBook extends Book {
/**
* Map known peers to their known protocols.
* @type {Map<string, Map<string, Buffer>>}
*
* @type {Map<string, Map<string, Uint8Array>>}
*/
this.data = new Map()
}
/**
* Set metadata key and value of a provided peer.
*
* @override
* @param {PeerId} peerId
* @param {string} key metadata key
* @param {Buffer} value metadata value
* @param {string} key - metadata key
* @param {Uint8Array} value - metadata value
* @returns {ProtoBook}
*/
set (peerId, key, value) {
@ -57,7 +59,7 @@ class MetadataBook extends Book {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (typeof key !== 'string' || !Buffer.isBuffer(value)) {
if (typeof key !== 'string' || !(value instanceof Uint8Array)) {
log.error('valid key and value must be provided to store data')
throw errcode(new Error('valid key and value must be provided'), ERR_INVALID_PARAMETERS)
}
@ -69,6 +71,7 @@ class MetadataBook extends Book {
/**
* Set data into the datastructure
*
* @override
*/
_setValue (peerId, key, value, { emit = true } = {}) {
@ -77,7 +80,7 @@ class MetadataBook extends Book {
const recMap = rec.get(key)
// Already exists and is equal
if (recMap && value.equals(recMap)) {
if (recMap && uint8ArrayEquals(value, recMap)) {
log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`)
return
}
@ -90,8 +93,9 @@ class MetadataBook extends Book {
/**
* Get the known data of a provided peer.
*
* @param {PeerId} peerId
* @returns {Map<string, Buffer>}
* @returns {Map<string, Uint8Array>}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
@ -103,9 +107,10 @@ class MetadataBook extends Book {
/**
* Get specific metadata value, if it exists
*
* @param {PeerId} peerId
* @param {string} key
* @returns {Buffer}
* @returns {Uint8Array}
*/
getValue (peerId, key) {
if (!PeerId.isPeerId(peerId)) {
@ -118,6 +123,7 @@ class MetadataBook extends Book {
/**
* Deletes the provided peer from the book.
*
* @param {PeerId} peerId
* @returns {boolean}
*/
@ -137,6 +143,7 @@ class MetadataBook extends Book {
/**
* Deletes the provided peer metadata key from the book.
*
* @param {PeerId} peerId
* @param {string} key
* @returns {boolean}

View File

@ -26,13 +26,14 @@ const Protocols = require('./pb/proto-book.proto')
*/
class PersistentPeerStore extends PeerStore {
/**
* @constructor
* @class
* @param {Object} properties
* @param {Datastore} properties.datastore Datastore to persist data.
* @param {number} [properties.threshold = 5] Number of dirty peers allowed before commit data.
* @param {PeerId} properties.peerId
* @param {Datastore} properties.datastore - Datastore to persist data.
* @param {number} [properties.threshold = 5] - Number of dirty peers allowed before commit data.
*/
constructor ({ datastore, threshold = 5 }) {
super()
constructor ({ peerId, datastore, threshold = 5 }) {
super({ peerId })
/**
* Backend datastore used to persist data.
@ -46,6 +47,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Peers metadata changed mapping peer identifers to metadata changed.
*
* @type {Map<string, Set<string>>}
*/
this._dirtyMetadata = new Map()
@ -56,7 +58,8 @@ class PersistentPeerStore extends PeerStore {
/**
* Start Persistent PeerStore.
* @return {Promise<void>}
*
* @returns {Promise<void>}
*/
async start () {
log('PeerStore is starting')
@ -64,7 +67,7 @@ class PersistentPeerStore extends PeerStore {
// Handlers for dirty peers
this.on('change:protocols', this._addDirtyPeer)
this.on('change:multiaddrs', this._addDirtyPeer)
this.on('change:pubkey', this._addDirtyPeer)
this.on('change:pubkey', this._addDirtyPeerKey)
this.on('change:metadata', this._addDirtyPeerMetadata)
// Load data
@ -75,6 +78,11 @@ class PersistentPeerStore extends PeerStore {
log('PeerStore started')
}
/**
* Stop Persistent PeerStore.
*
* @returns {Promise<void>}
*/
async stop () {
log('PeerStore is stopping')
this.removeAllListeners()
@ -84,6 +92,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add modified peer to the dirty set
*
* @private
* @param {Object} params
* @param {PeerId} params.peerId
@ -102,8 +111,35 @@ class PersistentPeerStore extends PeerStore {
}
}
/**
* Add modified peer key to the dirty set
*
* @private
* @param {Object} params
* @param {PeerId} params.peerId
*/
_addDirtyPeerKey ({ peerId }) {
// Not add if inline key available
if (peerId.hasInlinePublicKey()) {
return
}
const peerIdstr = peerId.toB58String()
log('add dirty peer key', peerIdstr)
this._dirtyPeers.add(peerIdstr)
if (this._dirtyPeers.size >= this.threshold) {
// Commit current data
this._commitData().catch(err => {
log.error('error committing data', err)
})
}
}
/**
* Add modified metadata peer to the set.
*
* @private
* @param {Object} params
* @param {PeerId} params.peerId
@ -130,9 +166,9 @@ class PersistentPeerStore extends PeerStore {
/**
* Add all the peers current data to a datastore batch and commit it.
*
* @private
* @param {Array<string>} peers
* @return {Promise<void>}
* @returns {Promise<void>}
*/
async _commitData () {
const commitPeers = Array.from(this._dirtyPeers)
@ -154,7 +190,7 @@ class PersistentPeerStore extends PeerStore {
this._batchAddressBook(peerId, batch)
// Key Book
this._batchKeyBook(peerId, batch)
!peerId.hasInlinePublicKey() && this._batchKeyBook(peerId, batch)
// Metadata Book
this._batchMetadataBook(peerId, batch)
@ -169,6 +205,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add address book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@ -177,19 +214,24 @@ class PersistentPeerStore extends PeerStore {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_ADDRESS}${b32key}`)
const addresses = this.addressBook.get(peerId)
const entry = this.addressBook.data.get(peerId.toB58String())
try {
// Deleted from the book
if (!addresses) {
if (!entry) {
batch.delete(key)
return
}
const encodedData = Addresses.encode({
addrs: addresses.map((address) => ({
multiaddr: address.multiaddr.buffer
}))
addrs: entry.addresses.map((address) => ({
multiaddr: address.multiaddr.bytes,
isCertified: address.isCertified
})),
certified_record: entry.record ? {
seq: entry.record.seqNumber,
raw: entry.record.raw
} : undefined
})
batch.put(key, encodedData)
@ -200,6 +242,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add Key book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@ -225,6 +268,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add metadata book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@ -251,6 +295,7 @@ class PersistentPeerStore extends PeerStore {
/**
* Add proto book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
@ -278,11 +323,12 @@ class PersistentPeerStore extends PeerStore {
/**
* Process datastore entry and add its data to the correct book.
*
* @private
* @param {Object} params
* @param {Key} params.key datastore key
* @param {Buffer} params.value datastore value stored
* @return {Promise<void>}
* @param {Key} params.key - datastore key
* @param {Uint8Array} params.value - datastore value stored
* @returns {Promise<void>}
*/
async _processDatastoreEntry ({ key, value }) {
try {
@ -296,9 +342,16 @@ class PersistentPeerStore extends PeerStore {
this.addressBook._setData(
peerId,
decoded.addrs.map((address) => ({
multiaddr: multiaddr(address.multiaddr)
})),
{
addresses: decoded.addrs.map((address) => ({
multiaddr: multiaddr(address.multiaddr),
isCertified: Boolean(address.isCertified)
})),
record: decoded.certified_record ? {
raw: decoded.certified_record.raw,
seqNumber: decoded.certified_record.seq
} : undefined
},
{ emit: false })
break
case 'keys':

View File

@ -4,11 +4,29 @@ const protons = require('protons')
const message = `
message Addresses {
// Address represents a single multiaddr.
message Address {
required bytes multiaddr = 1;
// Flag to indicate if the address comes from a certified source.
optional bool isCertified = 2;
}
// CertifiedRecord contains a serialized signed PeerRecord used to
// populate the signedAddrs list.
message CertifiedRecord {
// The Seq counter from the signed PeerRecord envelope
uint64 seq = 1;
// The serialized bytes of the SignedEnvelope containing the PeerRecord.
bytes raw = 2;
}
// The known multiaddrs.
repeated Address addrs = 1;
// The most recently received signed PeerRecord.
CertifiedRecord certified_record = 2;
}
`

View File

@ -16,13 +16,14 @@ const {
/**
* The ProtoBook is responsible for keeping the known supported
* protocols of a peer.
*
* @fires ProtoBook#change:protocols
*/
class ProtoBook extends Book {
/**
* @constructor
* @param {PeerStore} peerStore
*/
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the ProtoBook to emit:
@ -37,6 +38,7 @@ class ProtoBook extends Book {
/**
* Map known peers to their known protocols.
*
* @type {Map<string, Set<string>>}
*/
this.data = new Map()
@ -45,6 +47,7 @@ class ProtoBook extends Book {
/**
* Set known protocols of a provided peer.
* If the peer was not known before, it will be added.
*
* @override
* @param {PeerId} peerId
* @param {Array<string>} protocols
@ -83,6 +86,7 @@ class ProtoBook extends Book {
/**
* Adds known protocols of a provided peer.
* If the peer was not known before, it will be added.
*
* @param {PeerId} peerId
* @param {Array<string>} protocols
* @returns {ProtoBook}

View File

@ -14,12 +14,13 @@ const { PROTOCOL, PING_LENGTH } = require('./constants')
/**
* Ping a given peer and wait for its response, getting the operation latency.
*
* @param {Libp2p} node
* @param {PeerId} peer
* @returns {Promise<Number>}
* @param {PeerId|multiaddr} peer
* @returns {Promise<number>}
*/
async function ping (node, peer) {
log('dialing %s to %s', PROTOCOL, peer.toB58String())
log('dialing %s to %s', PROTOCOL, peer.toB58String ? peer.toB58String() : peer)
const { stream } = await node.dialProtocol(peer, PROTOCOL)
@ -44,6 +45,7 @@ async function ping (node, peer) {
/**
* Subscribe ping protocol handler.
*
* @param {Libp2p} node
*/
function mount (node) {
@ -52,6 +54,7 @@ function mount (node) {
/**
* Unsubscribe ping protocol handler.
*
* @param {Libp2p} node
*/
function unmount (node) {

View File

@ -64,7 +64,7 @@ node -e "require('libp2p/src/pnet').generate(process.stdout)" > swarm.key
```js
const writeKey = require('libp2p/src/pnet').generate
const swarmKey = Buffer.alloc(95)
const swarmKey = new Uint8Array(95)
writeKey(swarmKey)
fs.writeFileSync('swarm.key', swarmKey)
```

View File

@ -1,36 +1,37 @@
'use strict'
const { Buffer } = require('buffer')
const debug = require('debug')
const Errors = require('./errors')
const xsalsa20 = require('xsalsa20')
const KEY_LENGTH = require('./key-generator').KEY_LENGTH
const uint8ArrayFromString = require('uint8arrays/from-string')
const uint8ArrayToString = require('uint8arrays/to-string')
const log = debug('libp2p:pnet')
log.trace = debug('libp2p:pnet:trace')
log.error = debug('libp2p:pnet:err')
/**
* Creates a stream iterable to encrypt messages in a private network
* Creates a stream iterable to encrypt messages in a private network
*
* @param {Buffer} nonce The nonce to use in encryption
* @param {Buffer} psk The private shared key to use in encryption
* @param {Uint8Array} nonce - The nonce to use in encryption
* @param {Uint8Array} psk - The private shared key to use in encryption
* @returns {*} a through iterable
*/
module.exports.createBoxStream = (nonce, psk) => {
const xor = xsalsa20(nonce, psk)
return (source) => (async function * () {
for await (const chunk of source) {
yield Buffer.from(xor.update(chunk.slice()))
yield Uint8Array.from(xor.update(chunk.slice()))
}
})()
}
/**
* Creates a stream iterable to decrypt messages in a private network
* Creates a stream iterable to decrypt messages in a private network
*
* @param {Buffer} nonce The nonce of the remote peer
* @param {Buffer} psk The private shared key to use in decryption
* @param {Uint8Array} nonce - The nonce of the remote peer
* @param {Uint8Array} psk - The private shared key to use in decryption
* @returns {*} a through iterable
*/
module.exports.createUnboxStream = (nonce, psk) => {
@ -39,15 +40,15 @@ module.exports.createUnboxStream = (nonce, psk) => {
log.trace('Decryption enabled')
for await (const chunk of source) {
yield Buffer.from(xor.update(chunk.slice()))
yield Uint8Array.from(xor.update(chunk.slice()))
}
})()
}
/**
* Decode the version 1 psk from the given Buffer
* Decode the version 1 psk from the given Uint8Array
*
* @param {Buffer} pskBuffer
* @param {Uint8Array} pskBuffer
* @throws {INVALID_PSK}
* @returns {Object} The PSK metadata (tag, codecName, psk)
*/
@ -58,10 +59,10 @@ module.exports.decodeV1PSK = (pskBuffer) => {
// from the buffer line by line to evaluate the next line
// programmatically instead of making assumptions about the
// encodings of each line.
const metadata = pskBuffer.toString().split(/(?:\r\n|\r|\n)/g)
const metadata = uint8ArrayToString(pskBuffer).split(/(?:\r\n|\r|\n)/g)
const pskTag = metadata.shift()
const codec = metadata.shift()
const psk = Buffer.from(metadata.shift(), 'hex')
const psk = uint8ArrayFromString(metadata.shift(), 'base16')
if (psk.byteLength !== KEY_LENGTH) {
throw new Error(Errors.INVALID_PSK)

View File

@ -17,7 +17,7 @@ const handshake = require('it-handshake')
const { NONCE_LENGTH } = require('./key-generator')
const debug = require('debug')
const log = debug('libp2p:pnet')
log.err = debug('libp2p:pnet:err')
log.error = debug('libp2p:pnet:err')
/**
* Takes a Private Shared Key (psk) and provides a `protect` method
@ -25,8 +25,8 @@ log.err = debug('libp2p:pnet:err')
*/
class Protector {
/**
* @param {Buffer} keyBuffer The private shared key buffer
* @constructor
* @param {Uint8Array} keyBuffer - The private shared key buffer
* @class
*/
constructor (keyBuffer) {
const decodedPSK = decodeV1PSK(keyBuffer)
@ -39,7 +39,7 @@ class Protector {
* between its two peers from the PSK the Protector instance was
* created with.
*
* @param {Connection} connection The connection to protect
* @param {Connection} connection - The connection to protect
* @returns {*} A protected duplex iterable
*/
async protect (connection) {
@ -69,7 +69,7 @@ class Protector {
// Decrypt all inbound traffic
createUnboxStream(remoteNonce, this.psk),
external
)
).catch(log.error)
return internal
}

View File

@ -2,15 +2,20 @@
const crypto = require('libp2p-crypto')
const KEY_LENGTH = 32
const uint8ArrayToString = require('uint8arrays/to-string')
const uint8ArrayFromString = require('uint8arrays/from-string')
/**
* Generates a PSK that can be used in a libp2p-pnet private network
* @param {Writer} writer An object containing a `write` method
*
* @param {Uint8Array} bytes - An object to write the psk into
* @returns {void}
*/
function generate (writer) {
const psk = crypto.randomBytes(KEY_LENGTH).toString('hex')
writer.write('/key/swarm/psk/1.0.0/\n/base16/\n' + psk)
function generate (bytes) {
const psk = uint8ArrayToString(crypto.randomBytes(KEY_LENGTH), 'base16')
const key = uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\n' + psk)
bytes.set(key)
}
module.exports = generate

42
src/pubsub-adapter.js Normal file
View File

@ -0,0 +1,42 @@
'use strict'
// Pubsub adapter to keep API with handlers while not removed.
module.exports = (PubsubRouter, libp2p, options) => {
class Pubsub extends PubsubRouter {
/**
* Subscribes to a given topic.
*
* @override
* @param {string} topic
* @param {function(msg: InMessage)} [handler]
* @returns {void}
*/
subscribe (topic, handler) {
// Bind provided handler
handler && this.on(topic, handler)
super.subscribe(topic)
}
/**
* Unsubscribe from the given topic.
*
* @override
* @param {string} topic
* @param {function(msg: InMessage)} [handler]
* @returns {void}
*/
unsubscribe (topic, handler) {
if (!handler) {
this.removeAllListeners(topic)
} else {
this.removeListener(topic, handler)
}
if (this.listenerCount(topic) === 0) {
super.unsubscribe(topic)
}
}
}
return new Pubsub(libp2p, options)
}

View File

@ -1,105 +0,0 @@
'use strict'
const { Buffer } = require('buffer')
const errCode = require('err-code')
const { messages, codes } = require('./errors')
module.exports = (node, Pubsub, config) => {
const pubsub = new Pubsub(node.peerId, node.registrar, config)
return {
/**
* Subscribe the given handler to a pubsub topic
* @param {string} topic
* @param {function} handler The handler to subscribe
* @returns {void}
*/
subscribe: (topic, handler) => {
if (!node.isStarted() && !pubsub.started) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.PUBSUB_NOT_STARTED)
}
if (pubsub.listenerCount(topic) === 0) {
pubsub.subscribe(topic)
}
pubsub.on(topic, handler)
},
/**
* Unsubscribes from a pubsub topic
* @param {string} topic
* @param {function} [handler] The handler to unsubscribe from
*/
unsubscribe: (topic, handler) => {
if (!node.isStarted() && !pubsub.started) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.PUBSUB_NOT_STARTED)
}
if (!handler) {
pubsub.removeAllListeners(topic)
} else {
pubsub.removeListener(topic, handler)
}
if (pubsub.listenerCount(topic) === 0) {
pubsub.unsubscribe(topic)
}
},
/**
* Publish messages to the given topics.
* @param {Array<string>|string} topic
* @param {Buffer} data
* @returns {Promise<void>}
*/
publish: (topic, data) => {
if (!node.isStarted() && !pubsub.started) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.PUBSUB_NOT_STARTED)
}
try {
data = Buffer.from(data)
} catch (err) {
throw errCode(new Error('data must be convertible to a Buffer'), 'ERR_DATA_IS_NOT_VALID')
}
return pubsub.publish(topic, data)
},
/**
* Get a list of topics the node is subscribed to.
* @returns {Array<String>} topics
*/
getTopics: () => {
if (!node.isStarted() && !pubsub.started) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.PUBSUB_NOT_STARTED)
}
return pubsub.getTopics()
},
/**
* Get a list of the peer-ids that are subscribed to one topic.
* @param {string} topic
* @returns {Array<string>}
*/
getSubscribers: (topic) => {
if (!node.isStarted() && !pubsub.started) {
throw errCode(new Error(messages.NOT_STARTED_YET), codes.PUBSUB_NOT_STARTED)
}
return pubsub.getSubscribers(topic)
},
setMaxListeners (n) {
return pubsub.setMaxListeners(n)
},
_pubsub: pubsub,
start: () => pubsub.start(),
stop: () => pubsub.stop()
}
}

130
src/record/README.md Normal file
View File

@ -0,0 +1,130 @@
# Libp2p Records
Libp2p nodes need to store data in a public location (e.g. a DHT), or rely on potentially untrustworthy intermediaries to relay information over its lifetime. Accordingly, libp2p nodes need to be able to verify that the data came from a specific peer and that it hasn't been tampered with.
## Envelope
Libp2p provides an all-purpose data container called **envelope**. It was created to enable the distribution of verifiable records, which we can prove originated from the addressed peer itself. The envelope includes a signature of the data, so that its authenticity is verified.
This envelope stores a marshaled record implementing the [interface-record](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/record). These Records are designed to be serialized to bytes and placed inside of the envelopes before being shared with other peers.
You can read further about the envelope in [libp2p/specs#217](https://github.com/libp2p/specs/pull/217).
### Usage
- create an envelope with an instance of an [interface-record](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/record) implementation and prepare it for being exchanged:
```js
// interface-record implementation example with the "libp2p-example" namespace
const Record = require('libp2p-interfaces/src/record')
const fromString = require('uint8arrays/from-string')
class ExampleRecord extends Record {
constructor () {
super ('libp2p-example', fromString('0302', 'hex'))
}
marshal () {}
equals (other) {}
}
ExampleRecord.createFromProtobuf = () => {}
```
```js
const Envelope = require('libp2p/src/record/envelop')
const ExampleRecord = require('./example-record')
const rec = new ExampleRecord()
const e = await Envelope.seal(rec, peerId)
const wireData = e.marshal()
```
- consume a received envelope (`wireData`) and transform it back to a record:
```js
const Envelope = require('libp2p/src/record/envelop')
const ExampleRecord = require('./example-record')
const domain = 'libp2p-example'
let e
try {
e = await Envelope.openAndCertify(wireData, domain)
} catch (err) {}
const rec = ExampleRecord.createFromProtobuf(e.payload)
```
## Peer Record
All libp2p nodes keep a `PeerStore`, that among other information stores a set of known addresses for each peer, which can come from a variety of sources.
Libp2p peer records were created to enable the distribution of verifiable address records, which we can prove originated from the addressed peer itself. With such guarantees, libp2p is able to prioritize addresses based on their authenticity, with the most strict strategy being to only dial certified addresses (no strategies have been implemented at the time of writing).
A peer record contains the peers' publicly reachable listen addresses, and may be extended in the future to contain additional metadata relevant to routing. It also contains a `seqNumber` field, a timestamp per the spec, so that we can verify the most recent record.
You can read further about the Peer Record in [libp2p/specs#217](https://github.com/libp2p/specs/pull/217).
### Usage
- create a new Peer Record
```js
const PeerRecord = require('libp2p/src/record/peer-record')
const pr = new PeerRecord({
peerId: node.peerId,
multiaddrs: node.multiaddrs
})
```
- create a Peer Record from a protobuf
```js
const PeerRecord = require('libp2p/src/record/peer-record')
const pr = PeerRecord.createFromProtobuf(data)
```
### Libp2p Flows
#### Self Record
Once a libp2p node has started and is listening on a set of multiaddrs, its own peer record can be created.
The identify service is responsible for creating the self record when the identify protocol kicks in for the first time. This record will be stored for future needs of the identify protocol when connecting with other peers.
#### Self record Updates
**_NOT_YET_IMPLEMENTED_**
While creating peer records is fairly trivial, addresses are not static and might be modified at arbitrary times. This can happen via an Address Manager API, or even through AutoRelay/AutoNAT.
When a libp2p node changes its listen addresses, the identify service will be informed. Once that happens, the identify service creates a new self record and stores it. With the new record, the identify push/delta protocol will be used to communicate this change to the connected peers.
#### Subsystem receiving a record
Considering that a node can discover other peers' addresses from a variety of sources, Libp2p Peerstore can differentiate the addresses that were obtained through a signed peer record.
Once a record is received and its signature properly validated, its envelope is stored in the AddressBook in its byte representation. The `seqNumber` remains unmarshalled so that we can quickly compare it against incoming records to determine the most recent record.
The AddressBook Addresses will be updated with the content of the envelope with a certified property. This allows other subsystems to identify the known certified addresses of a peer.
#### Subsystem providing a record
Libp2p subsystems that exchange other peers information will provide the envelope that they received by those peers. As a result, other peers can verify if the envelope was really created by the addressed peer.
When a subsystem wants to provide a record, it will get it from the AddressBook, if it exists. Other subsystems are also able to provide the self record, since it is also stored in the AddressBook.
### Future Work
- Persistence only considering certified addresses?
- Peers may not know their own addresses. It's often impossible to automatically infer one's own public address, and peers may need to rely on third party peers to inform them of their observed public addresses.
- A peer may inadvertently or maliciously sign an address that they do not control. In other words, a signature isn't a guarantee that a given address is valid.
- Some addresses may be ambiguous. For example, addresses on a private subnet are valid within that subnet but are useless on the public internet.
- Once all these pieces are in place, we will also need a way to prioritize addresses based on their authenticity, that is, the dialer can prioritize self-certified addresses over addresses from an unknown origin.
- Modular dialer? (taken from go PR notes)
- With the modular dialer, users should easily be able to configure precedence. With dialer v1, anything we do to prioritise dials is gonna be spaghetti and adhoc. With the modular dialer, youd be able to specify the order of dials when instantiating the pipeline.
- Multiple parallel dials. We already have the issue where new addresses aren't added to existing dials.

View File

@ -0,0 +1,25 @@
'use strict'
const protons = require('protons')
const message = `
message Envelope {
// public_key is the public key of the keypair the enclosed payload was
// signed with.
bytes public_key = 1;
// payload_type encodes the type of payload, so that it can be deserialized
// deterministically.
bytes payload_type = 2;
// payload is the actual payload carried inside this envelope.
bytes payload = 3;
// signature is the signature produced by the private key corresponding to
// the enclosed public key, over the payload, prefixing a domain string for
// additional security.
bytes signature = 5;
}
`
module.exports = protons(message).Envelope

View File

@ -0,0 +1,182 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:envelope')
log.error = debug('libp2p:envelope:error')
const errCode = require('err-code')
const uint8arraysConcat = require('uint8arrays/concat')
const uint8arraysFromString = require('uint8arrays/from-string')
const cryptoKeys = require('libp2p-crypto/src/keys')
const PeerId = require('peer-id')
const varint = require('varint')
const uint8arraysEquals = require('uint8arrays/equals')
const { codes } = require('../../errors')
const Protobuf = require('./envelope.proto')
/**
* The Envelope is responsible for keeping an arbitrary signed record
* by a libp2p peer.
*/
class Envelope {
/**
* @class
* @param {object} params
* @param {PeerId} params.peerId
* @param {Uint8Array} params.payloadType
* @param {Uint8Array} params.payload - marshaled record
* @param {Uint8Array} params.signature - signature of the domain string :: type hint :: payload.
*/
constructor ({ peerId, payloadType, payload, signature }) {
this.peerId = peerId
this.payloadType = payloadType
this.payload = payload
this.signature = signature
// Cache
this._marshal = undefined
}
/**
* Marshal the envelope content.
*
* @returns {Uint8Array}
*/
marshal () {
if (this._marshal) {
return this._marshal
}
const publicKey = cryptoKeys.marshalPublicKey(this.peerId.pubKey)
this._marshal = Protobuf.encode({
public_key: publicKey,
payload_type: this.payloadType,
payload: this.payload,
signature: this.signature
})
return this._marshal
}
/**
* Verifies if the other Envelope is identical to this one.
*
* @param {Envelope} other
* @returns {boolean}
*/
equals (other) {
return uint8arraysEquals(this.peerId.pubKey.bytes, other.peerId.pubKey.bytes) &&
uint8arraysEquals(this.payloadType, other.payloadType) &&
uint8arraysEquals(this.payload, other.payload) &&
uint8arraysEquals(this.signature, other.signature)
}
/**
* Validate envelope data signature for the given domain.
*
* @param {string} domain
* @returns {Promise<boolean>}
*/
validate (domain) {
const signData = formatSignaturePayload(domain, this.payloadType, this.payload)
return this.peerId.pubKey.verify(signData, this.signature)
}
}
/**
* Helper function that prepares a Uint8Array to sign or verify a signature.
*
* @param {string} domain
* @param {Uint8Array} payloadType
* @param {Uint8Array} payload
* @returns {Uint8Array}
*/
const formatSignaturePayload = (domain, payloadType, payload) => {
// When signing, a peer will prepare a Uint8Array by concatenating the following:
// - The length of the domain separation string string in bytes
// - The domain separation string, encoded as UTF-8
// - The length of the payload_type field in bytes
// - The value of the payload_type field
// - The length of the payload field in bytes
// - The value of the payload field
domain = uint8arraysFromString(domain)
const domainLength = varint.encode(domain.byteLength)
const payloadTypeLength = varint.encode(payloadType.length)
const payloadLength = varint.encode(payload.length)
return uint8arraysConcat([
new Uint8Array(domainLength),
domain,
new Uint8Array(payloadTypeLength),
payloadType,
new Uint8Array(payloadLength),
payload
])
}
/**
* Unmarshal a serialized Envelope protobuf message.
*
* @param {Uint8Array} data
* @returns {Promise<Envelope>}
*/
Envelope.createFromProtobuf = async (data) => {
const envelopeData = Protobuf.decode(data)
const peerId = await PeerId.createFromPubKey(envelopeData.public_key)
return new Envelope({
peerId,
payloadType: envelopeData.payload_type,
payload: envelopeData.payload,
signature: envelopeData.signature
})
}
/**
* Seal marshals the given Record, places the marshaled bytes inside an Envelope
* and signs it with the given peerId's private key.
*
* @async
* @param {Record} record
* @param {PeerId} peerId
* @returns {Envelope}
*/
Envelope.seal = async (record, peerId) => {
const domain = record.domain
const payloadType = record.codec
const payload = record.marshal()
const signData = formatSignaturePayload(domain, payloadType, payload)
const signature = await peerId.privKey.sign(signData)
return new Envelope({
peerId,
payloadType,
payload,
signature
})
}
/**
* Open and certify a given marshalled envelope.
* Data is unmarshalled and the signature validated for the given domain.
*
* @param {Uint8Array} data
* @param {string} domain
* @returns {Envelope}
*/
Envelope.openAndCertify = async (data, domain) => {
const envelope = await Envelope.createFromProtobuf(data)
const valid = await envelope.validate(domain)
if (!valid) {
throw errCode(new Error('envelope signature is not valid for the given domain'), codes.ERR_SIGNATURE_NOT_VALID)
}
return envelope
}
module.exports = Envelope

View File

@ -0,0 +1,11 @@
'use strict'
const multicodec = require('multicodec')
// The domain string used for peer records contained in a Envelope.
module.exports.ENVELOPE_DOMAIN_PEER_RECORD = multicodec.getName(multicodec.LIBP2P_PEER_RECORD)
// The type hint used to identify peer records in a Envelope.
// Defined in https://github.com/multiformats/multicodec/blob/master/table.csv
// with name "libp2p-peer-record"
module.exports.ENVELOPE_PAYLOAD_TYPE_PEER_RECORD = Uint8Array.from([3, 1])

View File

@ -0,0 +1,103 @@
'use strict'
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const Record = require('libp2p-interfaces/src/record')
const arrayEquals = require('libp2p-utils/src/array-equals')
const Protobuf = require('./peer-record.proto')
const {
ENVELOPE_DOMAIN_PEER_RECORD,
ENVELOPE_PAYLOAD_TYPE_PEER_RECORD
} = require('./consts')
/**
* The PeerRecord is used for distributing peer routing records across the network.
* It contains the peer's reachable listen addresses.
*/
class PeerRecord extends Record {
/**
* @class
* @param {object} params
* @param {PeerId} params.peerId
* @param {Array<multiaddr>} params.multiaddrs - addresses of the associated peer.
* @param {number} [params.seqNumber] - monotonically-increasing sequence counter that's used to order PeerRecords in time.
*/
constructor ({ peerId, multiaddrs = [], seqNumber = Date.now() }) {
super(ENVELOPE_DOMAIN_PEER_RECORD, ENVELOPE_PAYLOAD_TYPE_PEER_RECORD)
this.peerId = peerId
this.multiaddrs = multiaddrs
this.seqNumber = seqNumber
// Cache
this._marshal = undefined
}
/**
* Marshal a record to be used in an envelope.
*
* @returns {Uint8Array}
*/
marshal () {
if (this._marshal) {
return this._marshal
}
this._marshal = Protobuf.encode({
peer_id: this.peerId.toBytes(),
seq: this.seqNumber,
addresses: this.multiaddrs.map((m) => ({
multiaddr: m.bytes
}))
})
return this._marshal
}
/**
* Returns true if `this` record equals the `other`.
*
* @param {Record} other
* @returns {boolean}
*/
equals (other) {
// Validate PeerId
if (!this.peerId.equals(other.peerId)) {
return false
}
// Validate seqNumber
if (this.seqNumber !== other.seqNumber) {
return false
}
// Validate multiaddrs
if (!arrayEquals(this.multiaddrs, other.multiaddrs)) {
return false
}
return true
}
}
/**
* Unmarshal Peer Record Protobuf.
*
* @param {Uint8Array} buf - marshaled peer record.
* @returns {PeerRecord}
*/
PeerRecord.createFromProtobuf = (buf) => {
// Decode
const peerRecord = Protobuf.decode(buf)
const peerId = PeerId.createFromBytes(peerRecord.peer_id)
const multiaddrs = (peerRecord.addresses || []).map((a) => multiaddr(a.multiaddr))
const seqNumber = peerRecord.seq
return new PeerRecord({ peerId, multiaddrs, seqNumber })
}
PeerRecord.DOMAIN = ENVELOPE_DOMAIN_PEER_RECORD
module.exports = PeerRecord

View File

@ -0,0 +1,29 @@
'use strict'
const protons = require('protons')
// PeerRecord messages contain information that is useful to share with other peers.
// Currently, a PeerRecord contains the public listen addresses for a peer, but this
// is expected to expand to include other information in the future.
// PeerRecords are designed to be serialized to bytes and placed inside of
// SignedEnvelopes before sharing with other peers.
const message = `
message PeerRecord {
// AddressInfo is a wrapper around a binary multiaddr. It is defined as a
// separate message to allow us to add per-address metadata in the future.
message AddressInfo {
bytes multiaddr = 1;
}
// peer_id contains a libp2p peer id in its binary representation.
bytes peer_id = 1;
// seq contains a monotonically-increasing sequence counter to order PeerRecords in time.
uint64 seq = 2;
// addresses is a list of public listen addresses for the peer.
repeated AddressInfo addresses = 3;
}
`
module.exports = protons(message).PeerRecord

View File

@ -18,7 +18,7 @@ class Registrar {
* @param {Object} props
* @param {PeerStore} props.peerStore
* @param {connectionManager} props.connectionManager
* @constructor
* @class
*/
constructor ({ peerStore, connectionManager }) {
// Used on topology to listen for protocol changes
@ -49,6 +49,7 @@ class Registrar {
/**
* Get a connection with a peer.
*
* @param {PeerId} peerId
* @returns {Connection}
*/
@ -58,8 +59,9 @@ class Registrar {
/**
* Register handlers for a set of multicodecs given
* @param {Topology} topology protocol topology
* @return {string} registrar identifier
*
* @param {Topology} topology - protocol topology
* @returns {string} registrar identifier
*/
register (topology) {
if (!Topology.isTopology(topology)) {
@ -79,8 +81,9 @@ class Registrar {
/**
* Unregister topology.
* @param {string} id registrar identifier
* @return {boolean} unregistered successfully
*
* @param {string} id - registrar identifier
* @returns {boolean} unregistered successfully
*/
unregister (id) {
return this.topologies.delete(id)
@ -88,6 +91,7 @@ class Registrar {
/**
* Remove a disconnected peer from the record
*
* @param {Connection} connection
* @param {Error} [error]
* @returns {void}

View File

@ -9,11 +9,11 @@ log.error = debug('libp2p:transports:error')
class TransportManager {
/**
* @constructor
* @class
* @param {object} options
* @param {Libp2p} options.libp2p The Libp2p instance. It will be passed to the transports.
* @param {Upgrader} options.upgrader The upgrader to provide to the transports
* @param {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] Address listen error tolerance.
* @param {Libp2p} options.libp2p - The Libp2p instance. It will be passed to the transports.
* @param {Upgrader} options.upgrader - The upgrader to provide to the transports
* @param {boolean} [options.faultTolerance = FAULT_TOLERANCE.FATAL_ALL] - Address listen error tolerance.
*/
constructor ({ libp2p, upgrader, faultTolerance = FAULT_TOLERANCE.FATAL_ALL }) {
this.libp2p = libp2p
@ -26,9 +26,9 @@ class TransportManager {
/**
* Adds a `Transport` to the manager
*
* @param {String} key
* @param {string} key
* @param {Transport} Transport
* @param {*} transportOptions Additional options to pass to the transport
* @param {*} transportOptions - Additional options to pass to the transport
* @returns {void}
*/
add (key, Transport, transportOptions = {}) {
@ -54,6 +54,7 @@ class TransportManager {
/**
* Stops all listeners
*
* @async
*/
async close () {
@ -75,6 +76,7 @@ class TransportManager {
/**
* Dials the given Multiaddr over it's supported transport
*
* @param {Multiaddr} ma
* @param {*} options
* @returns {Promise<Connection>}
@ -95,6 +97,7 @@ class TransportManager {
/**
* Returns all Multiaddr's the listeners are using
*
* @returns {Multiaddr[]}
*/
getAddrs () {
@ -109,6 +112,7 @@ class TransportManager {
/**
* Returns all the transports instances.
*
* @returns {Iterator<Transport>}
*/
getTransports () {
@ -117,6 +121,7 @@ class TransportManager {
/**
* Finds a transport that matches the given Multiaddr
*
* @param {Multiaddr} ma
* @returns {Transport|null}
*/
@ -130,6 +135,7 @@ class TransportManager {
/**
* Starts listeners for each listen Multiaddr.
*
* @async
*/
async listen () {
@ -206,6 +212,7 @@ class TransportManager {
/**
* Removes all transports from the manager.
* If any listeners are running, they will be closed.
*
* @async
*/
async removeAll () {
@ -222,6 +229,7 @@ class TransportManager {
* Enum Transport Manager Fault Tolerance values.
* FATAL_ALL should be used for failing in any listen circumstance.
* NO_FATAL should be used for not failing when not listening.
*
* @readonly
* @enum {number}
*/

View File

@ -5,6 +5,7 @@ const log = debug('libp2p:upgrader')
log.error = debug('libp2p:upgrader:error')
const Multistream = require('multistream-select')
const { Connection } = require('libp2p-interfaces/src/connection')
const ConnectionStatus = require('libp2p-interfaces/src/connection/status')
const PeerId = require('peer-id')
const pipe = require('it-pipe')
const errCode = require('err-code')
@ -14,7 +15,7 @@ const { codes } = require('./errors')
/**
* @typedef MultiaddrConnection
* @property {function} sink
* @property {Function} sink
* @property {AsyncIterator} source
* @property {*} conn
* @property {Multiaddr} remoteAddr
@ -34,7 +35,7 @@ class Upgrader {
* @param {Metrics} options.metrics
* @param {Map<string, Crypto>} options.cryptos
* @param {Map<string, Muxer>} options.muxers
* @param {function(Connection)} options.onConnection Called when a connection is upgraded
* @param {function(Connection)} options.onConnection - Called when a connection is upgraded
* @param {function(Connection)} options.onConnectionEnd
*/
constructor ({
@ -57,6 +58,7 @@ class Upgrader {
/**
* Upgrades an inbound connection
*
* @async
* @param {MultiaddrConnection} maConn
* @returns {Promise<Connection>}
@ -124,6 +126,7 @@ class Upgrader {
/**
* Upgrades an outbound connection
*
* @async
* @param {MultiaddrConnection} maConn
* @returns {Promise<Connection>}
@ -198,14 +201,15 @@ class Upgrader {
/**
* A convenience method for generating a new `Connection`
*
* @private
* @param {object} options
* @param {string} cryptoProtocol The crypto protocol that was negotiated
* @param {string} direction One of ['inbound', 'outbound']
* @param {MultiaddrConnection} maConn The transport layer connection
* @param {*} upgradedConn A duplex connection returned from multiplexer and/or crypto selection
* @param {Muxer} Muxer The muxer to be used for muxing
* @param {PeerId} remotePeer The peer the connection is with
* @param {string} options.cryptoProtocol - The crypto protocol that was negotiated
* @param {string} options.direction - One of ['inbound', 'outbound']
* @param {MultiaddrConnection} options.maConn - The transport layer connection
* @param {*} options.upgradedConn - A duplex connection returned from multiplexer and/or crypto selection
* @param {Muxer} options.Muxer - The muxer to be used for muxing
* @param {PeerId} options.remotePeer - The peer the connection is with
* @returns {Connection}
*/
_createConnection ({
@ -258,15 +262,25 @@ class Upgrader {
}
// Pipe all data through the muxer
pipe(upgradedConn, muxer, upgradedConn)
pipe(upgradedConn, muxer, upgradedConn).catch(log.error)
}
const _timeline = maConn.timeline
maConn.timeline = new Proxy(_timeline, {
set: (...args) => {
if (connection && args[1] === 'close' && args[2] && !_timeline.close) {
connection.stat.status = 'closed'
this.onConnectionEnd(connection)
// Wait for close to finish before notifying of the closure
(async () => {
try {
if (connection.stat.status === ConnectionStatus.OPEN) {
await connection.close()
}
} catch (err) {
log.error(err)
} finally {
this.onConnectionEnd(connection)
}
})()
}
return Reflect.set(...args)
@ -292,7 +306,7 @@ class Upgrader {
},
newStream: newStream || errConnectionNotMultiplexed,
getStreams: () => muxer ? muxer.streams : errConnectionNotMultiplexed,
close: err => maConn.close(err)
close: (err) => maConn.close(err)
})
this.onConnection(connection)
@ -302,9 +316,10 @@ class Upgrader {
/**
* Routes incoming streams to the correct handler
*
* @private
* @param {object} options
* @param {Connection} options.connection The connection the stream belongs to
* @param {Connection} options.connection - The connection the stream belongs to
* @param {Stream} options.stream
* @param {string} options.protocol
*/
@ -315,9 +330,10 @@ class Upgrader {
/**
* Attempts to encrypt the incoming `connection` with the provided `cryptos`.
*
* @private
* @async
* @param {PeerId} localPeer The initiators PeerId
* @param {PeerId} localPeer - The initiators PeerId
* @param {*} connection
* @param {Map<string, Crypto>} cryptos
* @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used
@ -344,9 +360,10 @@ class Upgrader {
/**
* Attempts to encrypt the given `connection` with the provided `cryptos`.
* The first `Crypto` module to succeed will be used
*
* @private
* @async
* @param {PeerId} localPeer The initiators PeerId
* @param {PeerId} localPeer - The initiators PeerId
* @param {*} connection
* @param {PeerId} remotePeerId
* @param {Map<string, Crypto>} cryptos
@ -374,10 +391,11 @@ class Upgrader {
/**
* Selects one of the given muxers via multistream-select. That
* muxer will be used for all future streams on the connection.
*
* @private
* @async
* @param {*} connection A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers The muxers to attempt multiplexing with
* @param {*} connection - A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers - The muxers to attempt multiplexing with
* @returns {*} A muxed connection
*/
async _multiplexOutbound (connection, muxers) {
@ -397,10 +415,11 @@ class Upgrader {
/**
* Registers support for one of the given muxers via multistream-select. The
* selected muxer will be used for all future streams on the connection.
*
* @private
* @async
* @param {*} connection A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers The muxers to attempt multiplexing with
* @param {*} connection - A basic duplex connection to multiplex
* @param {Map<string, Muxer>} muxers - The muxers to attempt multiplexing with
* @returns {*} A muxed connection
*/
async _multiplexInbound (connection, muxers) {

View File

@ -7,6 +7,9 @@ chai.use(require('chai-as-promised'))
const { expect } = chai
const sinon = require('sinon')
const delay = require('delay')
const pWaitFor = require('p-wait-for')
const peerUtils = require('../utils/creators/peer')
const mockConnection = require('../utils/mockConnection')
const baseOptions = require('../utils/base-options.browser')
@ -72,7 +75,7 @@ describe('Connection Manager', () => {
expect(libp2p.connectionManager.emit.callCount).to.equal(1)
const [event, connection] = libp2p.connectionManager.emit.getCall(0).args
expect(event).to.equal('peer:connect')
expect(connection.remotePeer.isEqual(remoteLibp2p.peerId)).to.equal(true)
expect(connection.remotePeer.equals(remoteLibp2p.peerId)).to.equal(true)
const libp2pConn = libp2p.connectionManager.get(remoteLibp2p.peerId)
expect(libp2pConn).to.exist()
@ -112,4 +115,148 @@ describe('libp2p.connections', () => {
await libp2p.stop()
await remoteLibp2p.stop()
})
describe('proactive connections', () => {
let nodes = []
beforeEach(async () => {
nodes = await peerUtils.createPeer({
number: 2,
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0/ws']
}
}
})
})
afterEach(async () => {
await Promise.all(nodes.map((node) => node.stop()))
sinon.reset()
})
it('should connect to all the peers stored in the PeerStore, if their number is below minConnections', async () => {
const [libp2p] = await peerUtils.createPeer({
fixture: false,
started: false,
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0/ws']
},
connectionManager: {
minConnections: 3
}
}
})
// Populate PeerStore before starting
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
await libp2p.start()
// Wait for peers to connect
await pWaitFor(() => libp2p.connectionManager.size === 2)
await libp2p.stop()
})
it('should connect to all the peers stored in the PeerStore until reaching the minConnections', async () => {
const minConnections = 1
const [libp2p] = await peerUtils.createPeer({
fixture: false,
started: false,
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0/ws']
},
connectionManager: {
minConnections
}
}
})
// Populate PeerStore before starting
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
await libp2p.start()
// Wait for peer to connect
await pWaitFor(() => libp2p.connectionManager.size === minConnections)
// Wait more time to guarantee no other connection happened
await delay(200)
expect(libp2p.connectionManager.size).to.eql(minConnections)
await libp2p.stop()
})
it('should connect to all the peers stored in the PeerStore until reaching the minConnections sorted', async () => {
const minConnections = 1
const [libp2p] = await peerUtils.createPeer({
fixture: false,
started: false,
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0/ws']
},
connectionManager: {
minConnections
}
}
})
// Populate PeerStore before starting
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
libp2p.peerStore.addressBook.set(nodes[1].peerId, nodes[1].multiaddrs)
libp2p.peerStore.protoBook.set(nodes[1].peerId, ['/protocol-min-conns'])
await libp2p.start()
// Wait for peer to connect
await pWaitFor(() => libp2p.connectionManager.size === minConnections)
// Should have connected to the peer with protocols
expect(libp2p.connectionManager.get(nodes[0].peerId)).to.not.exist()
expect(libp2p.connectionManager.get(nodes[1].peerId)).to.exist()
await libp2p.stop()
})
it('should connect to peers in the PeerStore when a peer disconnected', async () => {
const minConnections = 1
const autoDialInterval = 1000
const [libp2p] = await peerUtils.createPeer({
fixture: false,
config: {
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0/ws']
},
connectionManager: {
minConnections,
autoDialInterval
}
}
})
// Populate PeerStore after starting (discovery)
libp2p.peerStore.addressBook.set(nodes[0].peerId, nodes[0].multiaddrs)
// Wait for peer to connect
const conn = await libp2p.dial(nodes[0].peerId)
expect(libp2p.connectionManager.get(nodes[0].peerId)).to.exist()
await conn.close()
// Closed
await pWaitFor(() => libp2p.connectionManager.size === 0)
// Connected
await pWaitFor(() => libp2p.connectionManager.size === 1)
expect(libp2p.connectionManager.get(nodes[0].peerId)).to.exist()
await libp2p.stop()
})
})
})

View File

@ -58,7 +58,8 @@ describe('Connection Manager', () => {
config: {
modules: baseOptions.modules,
connectionManager: {
maxConnections: max
maxConnections: max,
minConnections: 2
}
},
started: false
@ -96,7 +97,8 @@ describe('Connection Manager', () => {
config: {
modules: baseOptions.modules,
connectionManager: {
maxConnections: max
maxConnections: max,
minConnections: 0
}
},
started: false

Some files were not shown because too many files have changed in this diff Show More