Compare commits

...

175 Commits

Author SHA1 Message Date
48656712ea chore: release version v0.29.4 2020-12-09 16:42:21 +01:00
1a5ae74741 chore: update contributors 2020-12-09 16:42:20 +01:00
8691465a52 feat: support custom listener options (#822)
* support custom listener options

* fix get listener options

* add doc to explain custom listener options

* add ut

* fix code style

* Apply suggestions from code review

Co-authored-by: Vasco Santos <vasco.santos@ua.pt>

* add missing comma

Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
2020-12-09 16:31:17 +01:00
6350a187c7 fix: dial self (#826) 2020-12-09 16:13:25 +01:00
8e3bb09279 chore: remove references to Solarnet (#820)
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
2020-12-01 19:10:47 +01:00
73204958ee docs: use Libp2p.create() in examples (#811) (#814) 2020-11-30 11:15:09 +01:00
e9e4b731a5 docs: fix JSDOc for stop and create (#812) (#813) 2020-11-27 10:50:35 +01:00
d0a9fada32 feat: custom and store self agent version + store self protocol version (#800)
* feat: custom and store self protocol and agent version

* fix: do not enable custom protocolVersion
2020-11-20 15:14:01 +01:00
824a444f56 docs(fix): fix contentRouting.getMany (#803) 2020-11-18 10:28:43 +01:00
fef54b2b2c chore: release version v0.29.3 2020-11-04 14:05:08 +01:00
8f29a667a1 chore: update contributors 2020-11-04 14:05:08 +01:00
093c0ea13f feat: resolve multiaddrs before dial (#782) 2020-11-04 13:54:50 +01:00
61c36f9e09 chore: release version v0.29.2 2020-10-23 15:40:54 +02:00
f82da56901 chore: update contributors 2020-10-23 15:40:53 +02:00
06f26e586f fix: cleanup open streams on conn close (#791) 2020-10-23 15:34:59 +02:00
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
a0cf83c640 chore: release version v0.28.2 2020-06-15 12:48:25 +02:00
b56cdda0ef chore: update contributors 2020-06-15 12:48:25 +02:00
b621fbdfdc revert: "fix: throw if no conn encryption module provided (#665)"
This reverts commit c038550fad.
2020-06-15 12:45:49 +02:00
24dd1d22c5 chore: release version v0.28.1 2020-06-12 16:48:30 +02:00
a1a1213bdc chore: update contributors 2020-06-12 16:48:29 +02:00
90a9d93968 chore: refactor ConnectionManager#get 2020-06-11 16:21:51 +02:00
8f680e20e9 feat: add ConnectionManager#getAll 2020-06-11 16:21:51 +02:00
afafd08943 chore: fix discv5 npm link
chore: update package name for discv5
2020-06-11 13:03:31 +02:00
c038550fad fix: throw if no conn encryption module provided (#665)
* fix: throw if no conn encryption module provided

* chore: address review

* chore: apply suggestions from code review

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

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-06-11 12:49:32 +02:00
d60a3215d0 chore: update interface datastore (#663)
* chore: update interface datastore

Updates to v1.x.x to not have multiple versions of this module in the ipfs browser bundle.

* fix: let batch commits complete before continuing tests

Batch commits are async but the tests weren't waiting for them to complete,
mainly because they are triggered by events.

There's no way that I can see of waiting for the batch commit to finish so
I've added delays to the tests. Not great but a start.

* chore: use error log

* test: wait for commit spies to complete

* chore: bump interface-datastore

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-06-10 23:39:40 +02:00
1e51295150 chore: use interop release 2020-06-08 12:20:48 +02:00
5299995998 chore: release version v0.28.0 2020-06-05 15:32:03 +02:00
64a66f08ac chore: update contributors 2020-06-05 15:32:03 +02:00
e2a63bab62 chore: release version v0.28.0-rc.0 2020-05-28 12:52:25 +02:00
294b032b0b chore: update contributors 2020-05-28 12:52:24 +02:00
72f37acd4a fix: always emit when a connection is made 2020-05-28 12:37:48 +02:00
52a615f922 fix: expose the muxed stream interface on inbound streams 2020-05-28 12:37:48 +02:00
698c1df1b4 feat: support dial only on transport manager to tolerate errors (#643)
* feat: support dial only on transport manager to tolerate errors

* chore: address review

* chore: add jsdoc to transport manager tolerance errors
2020-05-28 12:37:48 +02:00
7f4662f8d8 chore: fix api formatting (#644) 2020-05-28 12:37:48 +02:00
84b935f682 feat: metadata book (#638)
* feat: metadata book

* chore: address review

* chore: address review
2020-05-28 12:37:48 +02:00
0fbb59748e docs: update examples to 0.28.x api (#625)
* chore: update examples to 0.28 api

* chore: use libp2p-noise in examples

* chore: examples using multiaddrs property of libp2p

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

* docs: update language around secio in crypto example

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
56316b8c1e docs: migration 0.27 to 0.28 (#637)
* docs: migration 0.27 to 0.28

* chore: apply suggestions from code review

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

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
aaa1155633 chore: uncomment local peer public key after connect test 2020-05-28 12:37:48 +02:00
aa5e232479 chore: rewrite peer-store submit and retrieve docs (#605)
* chore: rewrite peer-store submit and retrieve docs

* chore: address review
2020-05-28 12:37:48 +02:00
eb7adcf1ab docs: libp2p components options specified 2020-05-28 12:37:48 +02:00
0be74e6a61 chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
6065923356 chore: integrate libp2p-keychain into js-libp2p (#633)
Integrates the libp2p-keychain codebase into this repo
2020-05-28 12:37:48 +02:00
2b45fee0ed fix: onConnect should not add addr to the addressBook 2020-05-28 12:37:48 +02:00
8bf5a70bb8 chore: integrate libp2p-keychain into js-libp2p (#633)
Integrates the libp2p-keychain codebase into this repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Richard Schneider <makaretu@gmail.com>
Co-authored-by: Maciej Krüger <mkg20001@gmail.com>
Co-authored-by: Victor Bjelkholm <victorbjelkholm@gmail.com>
Co-authored-by: Masahiro Saito <camelmasa@gmail.com>
Co-authored-by: Alan Shaw <alan.shaw@protocol.ai>
Co-authored-by: Hugo Dias <mail@hugodias.me>
Co-authored-by: Alberto Elias <hi@albertoelias.me>
Co-authored-by: Alex Potsides <alex@achingbrain.net>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
6627278a87 chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
65e87460b0 chore: add keys to keybook on connection upgraded 2020-05-28 12:37:48 +02:00
7b8d01697d chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
ce38033c10 feat: keybook 2020-05-28 12:37:48 +02:00
3f2b06dc26 chore: remove peer-info from package table 2020-05-28 12:37:48 +02:00
1e3d6f4b56 chore: apply suggestions from code review
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
48a9a3eecc refactor: peristent peer-store extended class and disabled by defaul 2020-05-28 12:37:48 +02:00
9ea9287bea chore: persist delete with test 2020-05-28 12:37:48 +02:00
5123a8357b feat: peerStore persistence 2020-05-28 12:37:48 +02:00
43630f1e0b test: use libp2p-noise (#585)
* chore: use libp2p-noise

* chore: address review
2020-05-28 12:37:48 +02:00
aaf62a40ec fix: libp2p connections getter 2020-05-28 12:37:48 +02:00
7fbd1556e8 fix: use libp2p.multiaddrs instead of listen
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
51474c334a chore: rename address functions and apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
d75cc97ced chore: update api docs 2020-05-28 12:37:48 +02:00
2a7967c1cc feat: address manager 2020-05-28 12:37:48 +02:00
9e9ec0b575 chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
d3a4bf0a3f chore: refactor connection manager and registrar 2020-05-28 12:37:48 +02:00
d33919d0b3 chore: use kad-dht with renamed peer-store properties 2020-05-28 12:37:48 +02:00
c215339a27 chore: rename peer-store properties 2020-05-28 12:37:48 +02:00
cb597b57d7 chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
12e48adafa chore: remove peer-info usage
BREAKING CHANGE: all API methods with peer-info parameters or return values were changed. You can check the API.md document, in order to check the new values to use
2020-05-28 12:37:48 +02:00
ed6d5bb4b4 chore: deprecate old peer store api (#598)
* chore: deprecate old peer-store api

BREAKING CHANGE: the peer-store api changed. Check the API docs for the new specification.

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
e9d225c9dc feat: address and proto books (#590)
* feat: address and proto books

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: minor fixes and initial tests added

* chore: integrate new peer-store with code using adapters for other modules

* chore: do not use peerstore.put on get-peer-info

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

* chore: add new peer store tests

* chore: apply suggestions from code review

Co-Authored-By: Jacob Heun <jacobheun@gmail.com>

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-05-28 12:37:48 +02:00
2aac3b0f69 chore: update libp2p-gossipsub url 2020-05-27 23:14:54 +02:00
9862653051 chore: add readme reference to migrations 2020-05-25 14:54:03 +02:00
b781911ac2 fix(example): rename misleading variable (#645)
* fix(example): rename misleading variable

* fix: typo
2020-05-25 13:14:07 +02:00
3fa1fa3a0b chore: increase max bundle size to 185kB 2020-05-11 11:00:13 +02:00
dfed982404 chore(deps-dev): bump libp2p-gossipsub from 0.2.6 to 0.3.0
Bumps [libp2p-gossipsub](https://github.com/ChainSafe/gossipsub-js) from 0.2.6 to 0.3.0.
- [Release notes](https://github.com/ChainSafe/gossipsub-js/releases)
- [Changelog](https://github.com/ChainSafe/gossipsub-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ChainSafe/gossipsub-js/compare/v0.2.6...v0.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 11:00:13 +02:00
60b6a55906 chore(deps-dev): bump aegir from 21.10.2 to 22.0.0 (#632)
Bumps [aegir](https://github.com/ipfs/aegir) from 21.10.2 to 22.0.0.
- [Release notes](https://github.com/ipfs/aegir/releases)
- [Changelog](https://github.com/ipfs/aegir/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ipfs/aegir/compare/v21.10.2...v22.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-08 10:52:49 +02:00
161 changed files with 12447 additions and 2506 deletions

View File

@ -4,21 +4,21 @@ const Libp2p = require('./src')
const { MULTIADDRS_WEBSOCKETS } = require('./test/fixtures/browser')
const Peers = require('./test/fixtures/peers')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const WebSockets = require('libp2p-websockets')
const Muxer = require('libp2p-mplex')
const Crypto = require('libp2p-secio')
const { NOISE: Crypto } = require('libp2p-noise')
const pipe = require('it-pipe')
let libp2p
const before = async () => {
// Use the last peer
const peerId = await PeerId.createFromJSON(Peers[Peers.length - 1])
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add(MULTIADDRS_WEBSOCKETS[0])
libp2p = new Libp2p({
peerInfo,
addresses: {
listen: [MULTIADDRS_WEBSOCKETS[0]]
},
peerId,
modules: {
transport: [WebSockets],
streamMuxer: [Muxer],
@ -45,9 +45,15 @@ const after = async () => {
}
module.exports = {
bundlesize: { maxSize: '179kB' },
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,382 @@
<a name="0.29.4"></a>
## [0.29.4](https://github.com/libp2p/js-libp2p/compare/v0.29.3...v0.29.4) (2020-12-09)
### Bug Fixes
* dial self ([#826](https://github.com/libp2p/js-libp2p/issues/826)) ([6350a18](https://github.com/libp2p/js-libp2p/commit/6350a18))
### Features
* custom and store self agent version + store self protocol version ([#800](https://github.com/libp2p/js-libp2p/issues/800)) ([d0a9fad](https://github.com/libp2p/js-libp2p/commit/d0a9fad))
* support custom listener options ([#822](https://github.com/libp2p/js-libp2p/issues/822)) ([8691465](https://github.com/libp2p/js-libp2p/commit/8691465))
<a name="0.29.3"></a>
## [0.29.3](https://github.com/libp2p/js-libp2p/compare/v0.29.2...v0.29.3) (2020-11-04)
### Features
* resolve multiaddrs before dial ([#782](https://github.com/libp2p/js-libp2p/issues/782)) ([093c0ea](https://github.com/libp2p/js-libp2p/commit/093c0ea))
<a name="0.29.2"></a>
## [0.29.2](https://github.com/libp2p/js-libp2p/compare/v0.29.1...v0.29.2) (2020-10-23)
### Bug Fixes
* cleanup open streams on conn close ([#791](https://github.com/libp2p/js-libp2p/issues/791)) ([06f26e5](https://github.com/libp2p/js-libp2p/commit/06f26e5))
<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)
### Reverts
* "fix: throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665))" ([b621fbd](https://github.com/libp2p/js-libp2p/commit/b621fbd))
<a name="0.28.1"></a>
## [0.28.1](https://github.com/libp2p/js-libp2p/compare/v0.28.0...v0.28.1) (2020-06-12)
### Bug Fixes
* throw if no conn encryption module provided ([#665](https://github.com/libp2p/js-libp2p/issues/665)) ([c038550](https://github.com/libp2p/js-libp2p/commit/c038550))
### Features
* add ConnectionManager#getAll ([8f680e2](https://github.com/libp2p/js-libp2p/commit/8f680e2))
<a name="0.28.0"></a>
# [0.28.0](https://github.com/libp2p/js-libp2p/compare/v0.28.0-rc.0...v0.28.0) (2020-06-05)
<a name="0.28.0-rc.0"></a>
# [0.28.0-rc.0](https://github.com/libp2p/js-libp2p/compare/v0.27.8...v0.28.0-rc.0) (2020-05-28)
### Bug Fixes
* always emit when a connection is made ([72f37ac](https://github.com/libp2p/js-libp2p/commit/72f37ac))
* expose the muxed stream interface on inbound streams ([52a615f](https://github.com/libp2p/js-libp2p/commit/52a615f))
* libp2p connections getter ([aaf62a4](https://github.com/libp2p/js-libp2p/commit/aaf62a4))
* onConnect should not add addr to the addressBook ([2b45fee](https://github.com/libp2p/js-libp2p/commit/2b45fee))
* use libp2p.multiaddrs instead of listen ([7fbd155](https://github.com/libp2p/js-libp2p/commit/7fbd155))
* **example:** rename misleading variable ([#645](https://github.com/libp2p/js-libp2p/issues/645)) ([b781911](https://github.com/libp2p/js-libp2p/commit/b781911))
### Chores
* deprecate old peer store api ([#598](https://github.com/libp2p/js-libp2p/issues/598)) ([ed6d5bb](https://github.com/libp2p/js-libp2p/commit/ed6d5bb))
* remove peer-info usage ([12e48ad](https://github.com/libp2p/js-libp2p/commit/12e48ad))
### Features
* address and proto books ([#590](https://github.com/libp2p/js-libp2p/issues/590)) ([e9d225c](https://github.com/libp2p/js-libp2p/commit/e9d225c))
* address manager ([2a7967c](https://github.com/libp2p/js-libp2p/commit/2a7967c))
* keybook ([ce38033](https://github.com/libp2p/js-libp2p/commit/ce38033))
* metadata book ([#638](https://github.com/libp2p/js-libp2p/issues/638)) ([84b935f](https://github.com/libp2p/js-libp2p/commit/84b935f))
* peerStore persistence ([5123a83](https://github.com/libp2p/js-libp2p/commit/5123a83))
* support dial only on transport manager to tolerate errors ([#643](https://github.com/libp2p/js-libp2p/issues/643)) ([698c1df](https://github.com/libp2p/js-libp2p/commit/698c1df))
### BREAKING CHANGES
* all API methods with peer-info parameters or return values were changed. You can check the API.md document, in order to check the new values to use
* the peer-store api changed. Check the API docs for the new specification.
* chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
* chore: apply suggestions from code review
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
<a name="0.27.8"></a>
## [0.27.8](https://github.com/libp2p/js-libp2p/compare/v0.27.7...v0.27.8) (2020-05-06)

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>
@ -35,9 +35,11 @@ We've come a long way, but this project is still in Alpha, lots of development i
The documentation in the master branch may contain changes from a pre-release.
If you are looking for the documentation of the latest release, you can view the latest release on [**npm**](https://www.npmjs.com/package/libp2p), or select the tag in github that matches the version you are looking for.
**Want to get started?** Check our [examples folder](/examples).
**Want to get started?** Check our [GETTING_STARTED.md](./doc/GETTING_STARTED.md) guide and [examples folder](/examples).
[**`Weekly Core Dev Calls`**](https://github.com/ipfs/pm/issues/650)
**Want to update libp2p in your project?** Check our [migrations folder](./doc/migrations).
[**`Weekly Core Dev Calls`**](https://github.com/libp2p/team-mgmt/issues/16)
## Lead Maintainer
@ -144,6 +146,7 @@ List of packages currently in existence for libp2p
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-webrtc-star/master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-websockets`](//github.com/libp2p/js-libp2p-websockets) | [![npm](https://img.shields.io/npm/v/libp2p-websockets.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-websockets/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-websockets/master)](https://travis-ci.com/libp2p/js-libp2p-websockets) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-websockets/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-websockets) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| **secure channels** |
| [`libp2p-noise`](//github.com/NodeFactoryIo/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/libp2p-noise.svg?maxAge=86400&style=flat-square)](//github.com/NodeFactoryIo/js-libp2p-noise/releases) | [![Deps](https://david-dm.org/NodeFactoryIo/js-libp2p-noise.svg?style=flat-square)](https://david-dm.org/NodeFactoryIo/js-libp2p-noise) | [![Travis CI](https://flat.badgen.net/travis/NodeFactoryIo/js-libp2p-noise/master)](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise) | [![codecov](https://codecov.io/gh/NodeFactoryIo/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/NodeFactoryIo/js-libp2p-noise) | N/A |
| [`libp2p-secio`](//github.com/libp2p/js-libp2p-secio) | [![npm](https://img.shields.io/npm/v/libp2p-secio.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-secio/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-secio/master)](https://travis-ci.com/libp2p/js-libp2p-secio) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-secio/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-secio) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) |
| **stream multiplexers** |
| [`libp2p-mplex`](//github.com/libp2p/js-libp2p-mplex) | [![npm](https://img.shields.io/npm/v/libp2p-mplex.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mplex/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mplex.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mplex) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mplex/master)](https://travis-ci.com/libp2p/js-libp2p-mplex) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mplex/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mplex) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
@ -152,7 +155,7 @@ List of packages currently in existence for libp2p
| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-kad-dht/master)](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`libp2p-mdns`](//github.com/libp2p/js-libp2p-mdns) | [![npm](https://img.shields.io/npm/v/libp2p-mdns.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-mdns/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-mdns.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-mdns) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-mdns/master)](https://travis-ci.com/libp2p/js-libp2p-mdns) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-mdns/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-mdns) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-webrtc-star`](//github.com/libp2p/js-libp2p-webrtc-star) | [![npm](https://img.shields.io/npm/v/libp2p-webrtc-star.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-webrtc-star/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-webrtc-star.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-webrtc-star) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-webrtc-star/master)](https://travis-ci.com/libp2p/js-libp2p-webrtc-star) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-webrtc-star) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| [`discv5`](//github.com/ChainSafe/discv5) | [![npm](https://img.shields.io/npm/v/discv5.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/discv5/releases) | [![Deps](https://david-dm.org/ChainSafe/discv5.svg?style=flat-square)](https://david-dm.org/ChainSafe/discv5) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/discv5/master)](https://travis-ci.com/ChainSafe/discv5) | [![codecov](https://codecov.io/gh/ChainSafe/discv5/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/discv5) | [Cayman Nava](mailto:caymannava@gmail.com) |
| [`@chainsafe/discv5`](//github.com/ChainSafe/discv5) | [![npm](https://img.shields.io/npm/v/@chainsafe/discv5.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/discv5/releases) | [![Deps](https://david-dm.org/ChainSafe/discv5.svg?style=flat-square)](https://david-dm.org/ChainSafe/discv5) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/discv5/master)](https://travis-ci.com/ChainSafe/discv5) | [![codecov](https://codecov.io/gh/ChainSafe/discv5/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/discv5) | [Cayman Nava](mailto:caymannava@gmail.com) |
| **content routing** |
| [`libp2p-delegated-content-routing`](//github.com/libp2p/js-libp2p-delegated-content-routing) | [![npm](https://img.shields.io/npm/v/libp2p-delegated-content-routing.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-delegated-content-routing/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-delegated-content-routing) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-delegated-content-routing/master)](https://travis-ci.com/libp2p/js-libp2p-delegated-content-routing) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-delegated-content-routing) | [Jacob Heun](mailto:jacobheun@gmail.com) |
| [`libp2p-kad-dht`](//github.com/libp2p/js-libp2p-kad-dht) | [![npm](https://img.shields.io/npm/v/libp2p-kad-dht.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-kad-dht/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-kad-dht.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-kad-dht) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-kad-dht/master)](https://travis-ci.com/libp2p/js-libp2p-kad-dht) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-kad-dht/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-kad-dht) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
@ -164,11 +167,9 @@ List of packages currently in existence for libp2p
| [`libp2p-crypto-secp256k1`](//github.com/libp2p/js-libp2p-crypto-secp256k1) | [![npm](https://img.shields.io/npm/v/libp2p-crypto-secp256k1.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-crypto-secp256k1/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-crypto-secp256k1) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-crypto-secp256k1/master)](https://travis-ci.com/libp2p/js-libp2p-crypto-secp256k1) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-crypto-secp256k1) | [Friedel Ziegelmayer](mailto:dignifiedquire@gmail.com) |
| **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) |
| [`peer-info`](//github.com/libp2p/js-peer-info) | [![npm](https://img.shields.io/npm/v/peer-info.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-peer-info/releases) | [![Deps](https://david-dm.org/libp2p/js-peer-info.svg?style=flat-square)](https://david-dm.org/libp2p/js-peer-info) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-peer-info/master)](https://travis-ci.com/libp2p/js-peer-info) | [![codecov](https://codecov.io/gh/libp2p/js-peer-info/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-peer-info) | [Vasco Santos](mailto:vasco.santos@moxy.studio) |
| **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/gossipsub-js) | [![npm](https://img.shields.io/npm/v/libp2p-gossipsub.svg?maxAge=86400&style=flat-square)](//github.com/ChainSafe/gossipsub-js/releases) | [![Deps](https://david-dm.org/ChainSafe/gossipsub-js.svg?style=flat-square)](https://david-dm.org/ChainSafe/gossipsub-js) | [![Travis CI](https://flat.badgen.net/travis/ChainSafe/gossipsub-js/master)](https://travis-ci.com/ChainSafe/gossipsub-js) | [![codecov](https://codecov.io/gh/ChainSafe/gossipsub-js/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/gossipsub-js) | [Cayman Nava](mailto:caymannava@gmail.com) |
| [`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** |
| [`libp2p-nat-mgnr`](//github.com/libp2p/js-libp2p-nat-mgnr) | [![npm](https://img.shields.io/npm/v/libp2p-nat-mgnr.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-nat-mgnr/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-nat-mgnr.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-nat-mgnr) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-nat-mgnr/master)](https://travis-ci.com/libp2p/js-libp2p-nat-mgnr) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-nat-mgnr/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-nat-mgnr) | N/A |
| [`libp2p-utils`](//github.com/libp2p/js-libp2p-utils) | [![npm](https://img.shields.io/npm/v/libp2p-utils.svg?maxAge=86400&style=flat-square)](//github.com/libp2p/js-libp2p-utils/releases) | [![Deps](https://david-dm.org/libp2p/js-libp2p-utils.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-utils) | [![Travis CI](https://flat.badgen.net/travis/libp2p/js-libp2p-utils/master)](https://travis-ci.com/libp2p/js-libp2p-utils) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p-utils/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-utils) | [Vasco Santos](mailto:santos.vasco10@gmail.com) |

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)

1406
doc/API.md

File diff suppressed because it is too large Load Diff

View File

@ -20,9 +20,12 @@
- [Customizing DHT](#customizing-dht)
- [Setup with Content and Peer Routing](#setup-with-content-and-peer-routing)
- [Setup with Relay](#setup-with-relay)
- [Setup with Keychain](#setup-with-keychain)
- [Configuring Dialing](#configuring-dialing)
- [Configuring Connection Manager](#configuring-connection-manager)
- [Configuring Transport Manager](#configuring-transport-manager)
- [Configuring Metrics](#configuring-metrics)
- [Configuring PeerStore](#configuring-peerstore)
- [Customizing Transports](#customizing-transports)
- [Configuration examples](#configuration-examples)
@ -49,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
@ -94,7 +97,8 @@ If you want to know more about libp2p stream multiplexing, you should read the f
Some available connection encryption protocols:
- [libp2p/js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)
- [NodeFactoryIo/js-libp2p-noise](https://github.com/NodeFactoryIo/js-libp2p-noise)
- [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).
@ -173,7 +177,7 @@ If you want to know more about libp2p DHT, you should read the following content
Some available pubsub routers are:
- [libp2p/js-libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub)
- [ChainSafe/gossipsub-js](https://github.com/ChainSafe/gossipsub-js)
- [ChainSafe/js-libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub)
If none of the available pubsub routers fulfills your needs, you can create a libp2p compatible one. A libp2p pubsub router just needs to be created on top of [libp2p/js-libp2p-pubsub](https://github.com/libp2p/js-libp2p-pubsub), which ensures `js-libp2p` API expectations.
@ -204,8 +208,12 @@ Moreover, the majority of the modules can be customized via option parameters. T
Besides the `modules` and `config`, libp2p allows other internal options and configurations:
- `datastore`: an instance of [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore/) modules.
- This is used in modules such as the DHT. If it is not provided, `js-libp2p` will use an in memory datastore.
- `peerInfo`: a previously created instance of [libp2p/js-peer-info](https://github.com/libp2p/js-peer-info).
- `peerId`: the identity of the node, an instance of [libp2p/js-peer-id](https://github.com/libp2p/js-peer-id).
- This is particularly useful if you want to reuse the same `peer-id`, as well as for modules like `libp2p-delegated-content-routing`, which need a `peer-id` in their instantiation.
- `addresses`: an object containing `listen`, `announce` and `noAnnounce` properties with `Array<string>`:
- `listen` addresses will be provided to the libp2p underlying transports for listening on them.
- `announce` addresses will be used to compute the advertises that the node should advertise to the network.
- `noAnnounce` addresses will be used as a filter to compute the advertises that the node should advertise to the network.
### Examples
@ -215,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
@ -224,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')
@ -236,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
@ -250,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]: {
@ -283,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: {
@ -292,11 +300,11 @@ const node = await Libp2p.create({
WebRTCStar
],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
},
config: {
peerDiscovery: {
webRTCStar: {
[WebRTCStar.tag]: {
enabled: true
}
}
@ -310,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: {
@ -337,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: {
@ -367,27 +375,27 @@ 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 PeerInfo = require('peer-info')
const PeerId = require('peer-id')
// create a peerInfo
const peerInfo = await PeerInfo.create()
// create a peerId
const peerId = await PeerId.create()
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO],
connEncryption: [NOISE],
contentRouting: [
new DelegatedContentRouter(peerInfo.id)
new DelegatedContentRouter(peerId)
],
peerRouting: [
new DelegatedPeerRouter()
],
},
peerInfo
peerId
})
```
@ -397,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)
@ -417,26 +425,71 @@ const node = await Libp2p.create({
})
```
#### Configuring Dialing
#### Setup with Keychain
Dialing in libp2p can be configured to limit the rate of dialing, and how long dials are allowed to take. The below configuration example shows the default values for the dialer.
Libp2p allows you to setup a secure keychain to manage your keys. The keychain configuration object should have the following properties:
| Name | Type | Description |
|------|------|-------------|
| pass | `string` | Passphrase to use in the keychain (minimum of 20 characters). |
| datastore | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) |
```js
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',
datastore: new LevelStore('path/to/store')
}
})
await libp2p.loadKeychain()
```
#### Configuring Dialing
Dialing in libp2p can be configured to limit the rate of dialing, and how long dials are allowed to take. The dialer configuration object should have the following properties:
| Name | Type | Description |
|------|------|-------------|
| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. |
| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. |
| dialTimeout | `number` | Second dial timeout per peer in ms. |
| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs |
The below configuration example shows how the dialer should be configured, with the current defaults:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const { dnsaddrResolver } = require('multiaddr/src/resolvers')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE]
},
dialer: {
maxParallelDials: 100, // How many multiaddrs we can dial in parallel
maxDialsPerPeer: 4, // How many multiaddrs we can dial per peer, in parallel
dialTimeout: 30e3 // 30 second dial timeout per peer
maxParallelDials: 100,
maxDialsPerPeer: 4,
dialTimeout: 30e3,
resolvers: {
dnsaddr: dnsaddrResolver
}
}
```
@ -448,13 +501,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,
@ -471,32 +524,101 @@ const node = await Libp2p.create({
})
```
#### Configuring Metrics
#### Configuring Transport Manager
Metrics are disabled in libp2p by default. You can enable and configure them as follows. Aside from enabled being `false` by default, the configuration options listed here are the current defaults.
The Transport Manager is responsible for managing the libp2p transports life cycle. This includes starting listeners for the provided listen addresses, closing these listeners and dialing using the provided transports. By default, if a libp2p node has a list of multiaddrs for listenning on and there are no valid transports for those multiaddrs, libp2p will throw an error on startup and shutdown. However, for some applications it is perfectly acceptable for libp2p nodes to start in dial only mode if all the listen multiaddrs failed. This error tolerance can be enabled as follows:
```js
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')}
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
transportManager: {
faultTolerance: FaultTolerance.NO_FATAL
}
})
```
#### Configuring Metrics
Metrics are disabled in libp2p by default. You can enable and configure them as follows:
| Name | Type | Description |
|------|------|-------------|
| enabled | `boolean` | Enabled metrics collection. |
| computeThrottleMaxQueueSize | `number` | How many messages a stat will queue before processing. |
| computeThrottleTimeout | `number` | Time in milliseconds a stat will wait, after the last item was added, before processing. |
| movingAverageIntervals | `Array<number>` | The moving averages that will be computed. |
| maxOldPeersRetention | `number` | How many disconnected peers we will retain stats for. |
The below configuration example shows how the metrics should be configured. Aside from enabled being `false` by default, the following default configuration options are listed below:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE]
},
metrics: {
enabled: true,
computeThrottleMaxQueueSize: 1000, // How many messages a stat will queue before processing
computeThrottleTimeout: 2000, // Time in milliseconds a stat will wait, after the last item was added, before processing
movingAverageIntervals: [ // The moving averages that will be computed
computeThrottleMaxQueueSize: 1000,
computeThrottleTimeout: 2000,
movingAverageIntervals: [
60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes
15 * 60 * 1000 // 15 minutes
],
maxOldPeersRetention: 50 // How many disconnected peers we will retain stats for
maxOldPeersRetention: 50
}
})
```
#### Configuring PeerStore
PeerStore persistence is disabled in libp2p by default. You can enable and configure it as follows. Aside from enabled being `false` by default, it will need an implementation of a [datastore](https://github.com/ipfs/interface-datastore). Take into consideration that using the memory datastore will be ineffective for persistence.
The threshold number represents the maximum number of "dirty peers" allowed in the PeerStore, i.e. peers that are not updated in the datastore. In this context, browser nodes should use a threshold of 1, since they might not "stop" properly in several scenarios and the PeerStore might end up with unflushed records when the window is closed.
| Name | Type | Description |
|------|------|-------------|
| persistence | `boolean` | Is persistence enabled. |
| threshold | `number` | Number of dirty peers allowed. |
The below configuration example shows how the PeerStore should be configured. Aside from persistence being `false` by default, the following default configuration options are listed below:
```js
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')
const LevelStore = require('datastore-level')
const node = await Libp2p.create({
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [NOISE]
},
datastore: new LevelStore('path/to/store'),
peerStore: {
persistence: true,
threshold: 5
}
})
```
@ -509,7 +631,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]
@ -517,7 +639,7 @@ const node = await Libp2p.create({
modules: {
transport: [WebRTCStar],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
},
config: {
transport: {
@ -529,13 +651,41 @@ const node = await Libp2p.create({
})
```
During Libp2p startup, transport listeners will be created for the configured listen multiaddrs. Some transports support custom listener options and you can set them using the `listenerOptions` in the transport configuration. For example, [libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star) transport listener supports the configuration of its underlying [simple-peer](https://github.com/feross/simple-peer) ice server(STUN/TURN) config as follows:
```js
const transportKey = WebRTCStar.prototype[Symbol.toStringTag]
const node = await Libp2p.create({
modules: {
transport: [WebRTCStar],
streamMuxer: [MPLEX],
connEncryption: [NOISE]
},
addresses: {
listen: ['/dns4/your-wrtc-star.pub/tcp/443/wss/p2p-webrtc-star'] // your webrtc dns multiaddr
},
config: {
transport: {
[transportKey]: {
listenerOptions: {
config: {
iceServers: [
{"urls": ["turn:YOUR.TURN.SERVER:3478"], "username": "YOUR.USER", "credential": "YOUR.PASSWORD"},
{"urls": ["stun:YOUR.STUN.SERVER:3478"], "username": "", "credential": ""}]
}
}
}
}
}
})
```
## Configuration examples
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

@ -69,23 +69,23 @@ If you want to know more about libp2p transports, you should read the following
Encryption is an important part of communicating on the libp2p network. Every connection must be encrypted to help ensure security for everyone. As such, Connection Encryption (Crypto) is a required component of libp2p.
There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-secio` module, which is widely supported across the various libp2p implementations.
There are a growing number of Crypto modules being developed for libp2p. As those are released they will be tracked in the [Connection Encryption section of the configuration readme](./CONFIGURATION.md#connection-encryption). For now, we are going to configure our node to use the `libp2p-noise` module.
```sh
npm install libp2p-secio
npm install libp2p-noise
```
With `libp2p-secio` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array:
With `libp2p-noise` installed, we can add it to our existing configuration by importing it and adding it to the `modules.connEncryption` array:
```js
const Libp2p = require('libp2p')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const node = await Libp2p.create({
modules: {
transport: [WebSockets],
connEncryption: [SECIO]
connEncryption: [NOISE]
}
})
```
@ -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,13 +139,16 @@ 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({
addresses: {
listen: ['/ip4/127.0.0.1/tcp/8000/ws']
},
modules: {
transport: [WebSockets],
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -154,6 +157,12 @@ const node = await Libp2p.create({
await node.start()
console.log('libp2p has started')
const listenAddrs = node.transportManager.getAddrs()
console.log('libp2p is listening on the following addresses: ', listenAddrs)
const advertiseAddrs = node.multiaddrs
console.log('libp2p is advertising the following addresses: ', advertiseAddrs)
// stop libp2p
await node.stop()
console.log('libp2p has stopped')
@ -188,27 +197,27 @@ 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')
// Known peers addresses
const bootstrapMultiaddrs = [
'/dns4/ams-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/dns4/lon-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'
]
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]: {
@ -223,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

@ -1,4 +1,4 @@
# Migrating to the new API
# Migrating to the libp2p@0.27 API
A migration guide for refactoring your application code from libp2p v0.26.x to v0.27.0.

View File

@ -0,0 +1,343 @@
# Migrating to the libp2p@0.28 API
A migration guide for refactoring your application code from libp2p v0.27.x to v0.28.0.
## Table of Contents
- [PeerStore API](#peerstore-api)
- [Migrating from Peer Info](#migrating-from-peer-info)
- [Create](#create)
- [API Implications](#api-implications)
- [Connection Manager and Registrar](#connection-manager-and-registrar)
- [Events](#events)
- [Module Updates](#module-updates)
## PeerStore API
In `libp2p@0.27` we integrated the PeerStore (former [peer-book](https://github.com/libp2p/js-peer-book)) into the codebase. By that time, it was not documented in the [API DOC](../API.md) since it kept the same API as the `peer-book` and it was expected to be completelly rewritten in `libp2p@0.28`.
Moving towards a separation of concerns regarding known peers' data, as well as enabling PeerStore persistence, the PeerStore is now divided into four main components: `AddressBook`, `ProtoBook`, `KeyBook` and `MetadataBook`. This resulted in API changes in the PeerStore, since each type of peer data should now be added in an atomic fashion.
### Adding a Peer
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
peerInfo.protocols.add('/ping/1.0.0')
peerInfo.protocols.add('/ping/2.0.0')
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/0')
libp2p.peerStore.put(peerInfo)
```
**After**
```js
const peerId = ...
const protocols = ['/ping/1.0.0', 'ping/2.0.0']
const multiaddrs = ['/ip4/127.0.0.1/tcp/0']
libp2p.peerStore.protoBook.add(peerId, protocols)
libp2p.peerStore.addressBook.add(peerId, multiaddrs)
```
### Getting a Peer
**Before**
```js
const peerId = ...
const peerInfo = libp2p.peerStore.get(peerId)
// { id: PeerId, multiaddrs: MultiaddrSet, protocols: Set<string>}
```
**After**
```js
const peerId = ...
const peer = libp2p.peerStore.get(peerId)
// { id: PeerId, addresses: Array<{ multiaddr: Multiaddr }>, protocols: Array<string> }
```
### Checking for a Peer
**Before**
```js
const peerId = ...
const hasData = libp2p.peerStore.has(peerId)
```
**After**
```js
const peerId = ...
const hasData = Boolean(libp2p.peerStore.get(peerId))
```
### Removing a Peer
**Before**
```js
libp2p.peerStore.remove(peerId)
```
**After**
```js
// Atomic
libp2p.peerStore.protoBook.delete(peerId)
libp2p.peerStore.addressBook.delete(peerId)
// Remove the peer and ALL of its associated data
libp2p.peerStore.delete(peerId)
```
### Get all known Peers
**Before**
```js
const peers = libp2p.peerStore.peers
// Map<string, PeerInfo>
```
**After**
```js
const peers = libp2p.peerStore.peers
// Similar to libp2p.peerStore.get()
// Map<string, { id: PeerId, addresses: Array<{ multiaddr: Multiaddr }>, protocols: Array<string> }
```
## Migrating from Peer Info
[`PeerInfo`][peer-info] is a libp2p peer abstraction layer that combines a [`PeerId`][peer-id] with known data of the peer, namely its multiaddrs and protocols. It has been used for a long time by `js-libp2p` and its modules to carry this data around the libp2p stack, as well as by the libp2p API, both for providing this data to the users or to receive it from them.
Since this PeerInfo instances were navigating through the entire codebases, some data inconsistencies could be observed in libp2p. Different libp2p subsystems were running with different visions of the known peers data. For instance, a libp2p subsystem receives a copy of this instance with the peer multiaddrs and protocols, but if new data of the peer is obtained from other subsystem, it would not be updated on the former. Moreover, considering that several subsystems were modifying the peer data, libp2p had no way to determine the accurate data.
Considering the complete revamp of the libp2p PeerStore towards its second version, the PeerStore now acts as the single source of truth, we do not need to carry [`PeerInfo`][peer-info] instances around. This also solves all the problems stated above, since subsystems will report new observations to the PeerStore.
### Create
While it was possible to create a libp2p node without providing a [`PeerInfo`][peer-info], there were 2 use cases where a [`PeerInfo`][peer-info] was provided when creating a libp2p node.
#### Using an existing PeerId
`libp2p.create` receives a `peerId` property instead of a `peerInfo` property.
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
const libp2p = await Libp2p.create({
peerInfo
// ...
})
```
**After**
```js
const peerId = ...
const libp2p = await Libp2p.create({
peerId
// ...
})
```
#### Providing listen addresses
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/0')
const libp2p = await Libp2p.create({
peerInfo
// ...
})
await libp2p.start()
```
**After**
```js
const peerId = ...
const libp2p = await Libp2p.create({
peerId,
addresses: {
listen: ['/ip4/127.0.0.1/tcp/0']
}
// ...
})
await libp2p.start()
```
There is also a bonus regarding the peer addresses. `libp2p@0.28` comes with an AddressManager that also allows the configuration of `announce` and `noAnnounce` addresses.
This was possible to achieve before, but in a hacky way by removing or adding addresses to the `peerInfo`, after the node starts.
**Before**
```js
const peerId = ...
const peerInfo = new PeerInfo(peerId)
peerInfo.multiaddrs.add('/ip4/127.0.0.1/tcp/8000')
const libp2p = await Libp2p.create({
peerInfo
// ...
})
await libp2p.start()
peerInfo.multiaddrs.add('/dns4/peer.io') // Announce
peerInfo.multiaddrs.delete('/ip4/127.0.0.1/tcp/8000') // Not announce
```
**After**
```js
const peerId = ...
const libp2p = await Libp2p.create({
peerId,
addresses: {
listen: ['/ip4/127.0.0.1/tcp/8000'],
announce: ['/dns4/peer.io'],
noAnnounce: ['/ip4/127.0.0.1/tcp/8000']
}
// ...
})
await libp2p.start()
```
### API Implications
#### Peer Dialing, Hangup and Ping
`libp2p.dial`, `libp2p.dialProtocol`, `libp2p.hangup` and `libp2p.ping` supported as the target parameter a [`PeerInfo`](peer-info), a [`PeerId`](peer-id), a [`Multiaddr`][multiaddr] and a string representation of the multiaddr. Considering that [`PeerInfo`](peer-info) is being removed from libp2p, all these methods will now support the other 3 possibilities.
There is one relevant aspect to consider with this change. When using a [`PeerId`](peer-id), the PeerStore **MUST** have known addresses for that peer in its AddressBook, so that it can perform the request. This was also true in the past, but it is important pointing it out because it might not be enough to switch from using [`PeerInfo`](peer-info) to [`PeerId`](peer-id). When using a [`PeerInfo`](peer-info), the PeerStore was not required to have the multiaddrs when they existed on the PeerInfo instance.
**Before**
```js
const peerInfo = ... // PeerInfo containing its multiaddrs
const connection = await libp2p.dial(peerInfo)
```
**After**
```js
const peerId = ...
// Known multiaddrs should be added to the PeerStore
libp2p.peerStore.addressBook.add(peerId, multiaddrs)
const connection = await libp2p.dial(peerId)
```
#### Content Routing and Peer Routing
Both [content-routing](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/content-routing) and [peer-routing](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/peer-routing) interfaces were modified to not return a ['PeerInfo'][peer-info] instance.
**Before**
```js
for await (const peerInfo of libp2p.contentRouting.findProviders(cid)) {
// peerInfo is a PeerInfo instance
}
```
**After**
```js
for await (const peer of libp2p.contentRouting.findProviders(cid)) {
// { id: PeerId, multiaddrs: Multiaddr[] }
}
```
**Before**
```js
const peerInfo = await libp2p.peerRouting.findPeer(peerId)
// peerInfo is a PeerInfo instance
```
**After**
```js
const peer = await libp2p.peerRouting.findPeer(peerId)
// { id: PeerId, multiaddrs: Multiaddr[] }
```
## Connection Manager and Registrar
Registrar was introduced in `libp2p@0.27` along with [libp2p topologies](https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/topology). `Registrar` and `ConnectionManager` were both listening on new connections and keeping their record of the open connections with other peers.
The registrar API was not documented in the [API DOC](../API.md). However, it exposed a useful method for some libp2p users, `libp2p.registrar.getConnection()`. On the other hand, the connection Manager did not provide any methods to access its stored connections. On `libp2p@0.28` we removed this data duplication and the connections are handled solely by the `ConnectionManager`.
**Before**
```js
const connection = libp2p.registrar.getConnection(peerId)
```
**After**
```js
const connection = libp2p.connectionManager.get(peerId)
```
## Events
### Connection Events
Libp2p emits events whenever new connections are established. These emitted events previously providing the [`PeerInfo`](peer-info) of the peer that connected. In `libp2p@0.28` these events are now emitted from the Connection Manager and will now emit the [`connection`](connection) itself.
**Before**
```js
libp2p.on('peer:connect', (peerInfo) => {
// PeerInfo instance
})
libp2p.on('peer:disconnect', (peerInfo) => {
// PeerInfo instance
})
```
**After**
```js
libp2p.connectionManager.on('peer:connect', (connection) => {
// Connection instance
})
libp2p.connectionManager.on('peer:disconnect', (connection) => {
// Connection instance
})
```
### Peer Discovery
**Before**
```js
libp2p.on('peer:discovery', (peerInfo) => {
// PeerInfo instance
})
```
**After**
```js
libp2p.on('peer:discovery', (peerId) => {
// peerId instance
})
```
## 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
[peer-info]: https://github.com/libp2p/js-peer-info

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

@ -2,8 +2,8 @@
/* eslint-disable no-console */
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle')
const multiaddr = require('multiaddr')
const createLibp2p = require('./libp2p-bundle')
const { stdinToStream, streamToConsole } = require('./stream')
async function run() {
@ -13,27 +13,25 @@ async function run() {
])
// Create a new libp2p node on localhost with a randomly chosen port
const peerDialer = new PeerInfo(idDialer)
peerDialer.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const nodeDialer = new Node({
peerInfo: peerDialer
const nodeDialer = await createLibp2p({
peerId: idDialer,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
})
// Create a PeerInfo with the listening peer's address
const peerListener = new PeerInfo(idListener)
peerListener.multiaddrs.add('/ip4/127.0.0.1/tcp/10333')
// Start the libp2p host
await nodeDialer.start()
// Output this node's address
console.log('Dialer ready, listening on:')
peerListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String())
nodeDialer.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idDialer.toB58String())
})
// Dial to the remote peer (the "listener")
const { stream } = await nodeDialer.dialProtocol(peerListener, '/chat/1.0.0')
const listenerMa = multiaddr(`/ip4/127.0.0.1/tcp/10333/p2p/${idListener.toB58String()}`)
const { stream } = await nodeDialer.dialProtocol(listenerMa, '/chat/1.0.0')
console.log('Dialer dialed to listener on protocol: /chat/1.0.0')
console.log('Type a message and see what happens')

View File

@ -3,25 +3,20 @@
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('../../..')
class Node extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [
TCP,
WS
],
streamMuxer: [ mplex ],
connEncryption: [ secio ]
}
}
super(defaultsDeep(_options, defaults))
async function createLibp2p(_options) {
const defaults = {
modules: {
transport: [TCP, WS],
streamMuxer: [mplex],
connEncryption: [NOISE],
},
}
return libp2p.create(defaultsDeep(_options, defaults))
}
module.exports = Node
module.exports = createLibp2p

View File

@ -2,22 +2,22 @@
/* eslint-disable no-console */
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle.js')
const createLibp2p = require('./libp2p-bundle.js')
const { stdinToStream, streamToConsole } = require('./stream')
async function run() {
// Create a new libp2p node with the given multi-address
const idListener = await PeerId.createFromJSON(require('./peer-id-listener'))
const peerListener = new PeerInfo(idListener)
peerListener.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
const nodeListener = new Node({
peerInfo: peerListener
const nodeListener = await createLibp2p({
peerId: idListener,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/10333']
}
})
// Log a message when a remote peer connects to us
nodeListener.on('peer:connect', (peerInfo) => {
console.log(peerInfo.id.toB58String())
nodeListener.connectionManager.on('peer:connect', (connection) => {
console.log('connected to: ', connection.remotePeer.toB58String())
})
// Handle messages for the protocol
@ -33,7 +33,7 @@ async function run() {
// Output listen addresses to the console
console.log('Listener ready, listening on:')
peerListener.multiaddrs.forEach((ma) => {
nodeListener.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + idListener.toB58String())
})
}

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,28 +4,28 @@
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')
// Find this list at: https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/config-nodejs.json
const bootstrapers = [
'/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',
'/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z',
'/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',
'/ip4/162.243.248.213/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',
'/ip4/128.199.219.111/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',
'/ip4/104.236.76.40/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64',
'/ip4/178.62.158.247/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/ip4/178.62.61.185/tcp/4001/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',
'/ip4/104.236.151.122/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt'
]
;(async () => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
connEncryption: [NOISE],
peerDiscovery: [Bootstrap]
},
config: {
@ -39,15 +39,13 @@ const bootstrapers = [
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String()) // Emitted when a peer has been found
node.connectionManager.on('peer:connect', (connection) => {
console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a peer has been found
})
node.on('peer:discovery', (peer) => {
node.on('peer:discovery', (peerId) => {
// No need to dial, autoDial is on
console.log('Discovered:', peer.id.toB58String())
console.log('Discovered:', peerId.toB58String())
})
await node.start()

View File

@ -4,27 +4,29 @@
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')
const createNode = async () => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
connEncryption: [NOISE],
peerDiscovery: [MulticastDNS]
},
config: {
peerDiscovery: {
mdns: {
[MulticastDNS.tag]: {
interval: 20e3,
enabled: true
}
}
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
return node
}
@ -35,8 +37,8 @@ const createNode = async () => {
createNode()
])
node1.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
node2.on('peer:discovery', (peer) => console.log('Discovered:', peer.id.toB58String()))
node1.on('peer:discovery', (peerId) => console.log('Discovered:', peerId.toB58String()))
node2.on('peer:discovery', (peerId) => console.log('Discovered:', peerId.toB58String()))
await Promise.all([
node1.start(),

View File

@ -4,11 +4,11 @@ A Peer Discovery module enables libp2p to find peers to connect to. Think of the
With these system, a libp2p node can both have a set of nodes to always connect on boot (bootstraper nodes), discover nodes through locality (e.g connected in the same LAN) or through serendipity (random walks on a DHT).
These mechanisms save configuration and enable a node to operate without any explicit dials, it will just work.
These mechanisms save configuration and enable a node to operate without any explicit dials, it will just work. Once new peers are discovered, their known data is stored in the peer's PeerStore.
## 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: [ SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ Bootstrap ]
},
config: {
@ -40,14 +40,11 @@ In this configuration, we use a `bootstrappers` array listing peers to connect _
```JavaScript
const bootstrapers = [
'/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ',
'/ip4/104.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z',
'/ip4/104.236.179.241/tcp/4001/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',
'/ip4/162.243.248.213/tcp/4001/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',
'/ip4/128.199.219.111/tcp/4001/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',
'/ip4/104.236.76.40/tcp/4001/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64',
'/ip4/178.62.158.247/tcp/4001/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/ip4/178.62.61.185/tcp/4001/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',
'/ip4/104.236.151.122/tcp/4001/p2p/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt'
]
```
@ -55,11 +52,14 @@ Now, once we create and start the node, we can listen for events such as `peer:d
```JavaScript
const node = await Libp2p.create({
peerInfo,
peerId,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ Bootstrap ]
},
config: {
@ -73,15 +73,13 @@ const node = await Libp2p.create({
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
node.on('peer:connect', (peer) => {
console.log('Connection established to:', peer.id.toB58String()) // Emitted when a peer has been found
node.connectionManager.on('peer:connect', (connection) => {
console.log('Connection established to:', connection.remotePeer.toB58String()) // Emitted when a new connection has been created
})
// Emitted when a peer has been found
node.on('peer:discovery', (peer) => {
console.log('Discovered:', peer.id.toB58String())
node.on('peer:discovery', (peerId) => {
// No need to dial, autoDial is on
console.log('Discovered:', peerId.toB58String())
})
await node.start()
@ -92,14 +90,17 @@ From running [1.js](./1.js), you should see the following:
```bash
> node 1.js
Discovered: QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Discovered: QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z
Discovered: QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM
Discovered: QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm
Discovered: QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu
Discovered: QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
Discovered: QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd
Discovered: QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
Discovered: QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx
Discovered: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN
Discovered: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb
Discovered: QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp
Discovered: QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa
Discovered: QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt
Connection established to: QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
Connection established to: QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN
Connection established to: QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp
Connection established to: QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa
Connection established to: QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt
Connection established to: QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb
```
## 2. MulticastDNS to find other peers in the network
@ -114,10 +115,13 @@ const MulticastDNS = require('libp2p-mdns')
const createNode = () => {
return Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
}
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
connEncryption: [ NOISE ],
peerDiscovery: [ MulticastDNS ]
},
config: {
@ -157,5 +161,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

@ -6,8 +6,7 @@
*/
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle')
const createLibp2p = require('./libp2p-bundle')
const pipe = require('it-pipe')
async function run() {
@ -17,28 +16,26 @@ async function run() {
])
// Dialer
const dialerPeerInfo = new PeerInfo(dialerId)
dialerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const dialerNode = new Node({
peerInfo: dialerPeerInfo
const dialerNode = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
peerId: dialerId
})
// Peer to Dial (the listener)
const listenerPeerInfo = new PeerInfo(listenerId)
const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' +
listenerId.toB58String()
listenerPeerInfo.multiaddrs.add(listenerMultiaddr)
// Add peer to Dial (the listener) into the PeerStore
const listenerMultiaddr = '/ip4/127.0.0.1/tcp/10333/p2p/' + listenerId.toB58String()
// Start the dialer libp2p node
await dialerNode.start()
console.log('Dialer ready, listening on:')
dialerPeerInfo.multiaddrs.forEach((ma) => console.log(ma.toString() +
dialerNode.multiaddrs.forEach((ma) => console.log(ma.toString() +
'/p2p/' + dialerId.toB58String()))
// Dial the listener node
console.log('Dialing to peer:', listenerMultiaddr.toString())
const { stream } = await dialerNode.dialProtocol(listenerPeerInfo, '/echo/1.0.0')
console.log('Dialing to peer:', listenerMultiaddr)
const { stream } = await dialerNode.dialProtocol(listenerMultiaddr, '/echo/1.0.0')
console.log('nodeA dialed to nodeB on protocol: /echo/1.0.0')

View File

@ -3,25 +3,21 @@
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('../../..')
class Node extends libp2p {
constructor (_options) {
const defaults = {
modules: {
transport: [
TCP,
WS
],
streamMuxer: [ mplex ],
connEncryption: [ secio ]
}
}
super(defaultsDeep(_options, defaults))
async function createLibp2p(_options) {
const defaults = {
modules: {
transport: [TCP, WS],
streamMuxer: [mplex],
connEncryption: [NOISE],
},
}
return libp2p.create(defaultsDeep(_options, defaults))
}
module.exports = Node
module.exports = createLibp2p

View File

@ -6,23 +6,23 @@
*/
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const Node = require('./libp2p-bundle')
const createLibp2p = require('./libp2p-bundle')
const pipe = require('it-pipe')
async function run() {
const listenerId = await PeerId.createFromJSON(require('./id-l'))
// Listener libp2p node
const listenerPeerInfo = new PeerInfo(listenerId)
listenerPeerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/10333')
const listenerNode = new Node({
peerInfo: listenerPeerInfo
const listenerNode = await createLibp2p({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/10333']
},
peerId: listenerId
})
// Log a message when we receive a connection
listenerNode.on('peer:connect', (peerInfo) => {
console.log('received dial to me from:', peerInfo.id.toB58String())
listenerNode.connectionManager.on('peer:connect', (connection) => {
console.log('received dial to me from:', connection.remotePeer.toB58String())
})
// Handle incoming connections for the protocol by piping from the stream
@ -33,7 +33,7 @@ async function run() {
await listenerNode.start()
console.log('Listener ready, listening on:')
listenerNode.peerInfo.multiaddrs.forEach((ma) => {
listenerNode.multiaddrs.forEach((ma) => {
console.log(ma.toString() + '/p2p/' + listenerId.toB58String())
})
}

View File

@ -3,21 +3,19 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const { NOISE } = require('libp2p-noise')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO]
connEncryption: [NOISE]
}
})
@ -32,6 +30,8 @@ const createNode = async () => {
createNode()
])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.handle('/a-protocol', ({ stream }) => {
pipe(
stream,
@ -43,7 +43,7 @@ const createNode = async () => {
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/a-protocol')
const { stream } = await node1.dialProtocol(node2.peerId, '/a-protocol')
await pipe(
['This information is sent out encrypted to the other peer'],

View File

@ -6,32 +6,26 @@ We call this usage a _connection upgrade_ where given a connection between peer
A byproduct of having these encrypted communications modules is that we can authenticate the peers we are dialing to. You might have noticed that every time we dial to a peer in libp2p space, we always use its PeerId at the end (e.g /ip4/127.0.0.1/tcp/89765/p2p/QmWCbVw1XZ8hiYBwwshPce2yaTDYTqTaP7GCHGpry3ykWb), this PeerId is generated by hashing the Public Key of the peer. With this, we can create a crypto challenge when dialing to another peer and prove that peer is the owner of a PrivateKey that matches the Public Key we know.
# 1. Set up encrypted communications with SECIO
# 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 module `libp2p-secio` to complete it, go ahead and `npm install libp2p-secio`.
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`.
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.
To add it to your libp2p configuration, all you have to do is:
To add them to your libp2p configuration, all you have to do is:
```JavaScript
const Libp2p = require('libp2p')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const createNode = () => {
return Libp2p.create({
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
// Attach secio as the crypto channel to use
connEncryption: [ 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.
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

@ -2,30 +2,40 @@ import 'babel-polyfill'
import Libp2p from 'libp2p'
import Websockets from 'libp2p-websockets'
import WebRTCStar from 'libp2p-webrtc-star'
import Secio from 'libp2p-secio'
import { NOISE } from 'libp2p-noise'
import Mplex from 'libp2p-mplex'
import Boostrap from 'libp2p-bootstrap'
import Bootstrap from 'libp2p-bootstrap'
document.addEventListener('DOMContentLoaded', async () => {
// Create our libp2p node
const libp2p = await Libp2p.create({
addresses: {
// 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: [
'/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: [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',
'/dns4/lon-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',
'/dns4/sfo-3.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',
'/dns4/sgp-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',
'/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',
'/dns4/nyc-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt'
]
}
}
@ -43,30 +53,24 @@ document.addEventListener('DOMContentLoaded', async () => {
output.textContent += `${txt.trim()}\n`
}
// 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
const webrtcAddr = '/ip4/0.0.0.0/tcp/9090/wss/p2p-webrtc-star'
libp2p.peerInfo.multiaddrs.add(webrtcAddr)
// Listen for new peers
libp2p.on('peer:discovery', (peerInfo) => {
log(`Found peer ${peerInfo.id.toB58String()}`)
libp2p.on('peer:discovery', (peerId) => {
log(`Found peer ${peerId.toB58String()}`)
})
// Listen for new connections to peers
libp2p.on('peer:connect', (peerInfo) => {
log(`Connected to ${peerInfo.id.toB58String()}`)
libp2p.connectionManager.on('peer:connect', (connection) => {
log(`Connected to ${connection.remotePeer.toB58String()}`)
})
// Listen for peers disconnecting
libp2p.on('peer:disconnect', (peerInfo) => {
log(`Disconnected from ${peerInfo.id.toB58String()}`)
libp2p.connectionManager.on('peer:disconnect', (connection) => {
log(`Disconnected from ${connection.remotePeer.toB58String()}`)
})
await libp2p.start()
status.innerText = 'libp2p started!'
log(`libp2p id is ${libp2p.peerInfo.id.toB58String()}`)
log(`libp2p id is ${libp2p.peerId.toB58String()}`)
// Export libp2p to the window so you can play with the API
window.libp2p = libp2p

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,11 +16,11 @@
"dependencies": {
"@babel/preset-env": "^7.8.3",
"libp2p": "../../",
"libp2p-bootstrap": "^0.10.3",
"libp2p-mplex": "^0.9.3",
"libp2p-secio": "^0.12.2",
"libp2p-webrtc-star": "^0.17.3",
"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

@ -4,22 +4,20 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const { NOISE } = require('libp2p-noise')
const KadDHT = require('libp2p-kad-dht')
const delay = require('delay')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
connEncryption: [NOISE],
dht: KadDHT
},
config: {
@ -40,16 +38,19 @@ const createNode = async () => {
createNode()
])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await Promise.all([
node1.dial(node2.peerInfo),
node2.dial(node3.peerInfo)
node1.dial(node2.peerId),
node2.dial(node3.peerId)
])
// The DHT routing tables need a moment to populate
await delay(100)
const peer = await node1.peerRouting.findPeer(node3.peerInfo.id)
const peer = await node1.peerRouting.findPeer(node3.peerId)
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`))
})();

View File

@ -4,8 +4,7 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const { NOISE } = require('libp2p-noise')
const CID = require('cids')
const KadDHT = require('libp2p-kad-dht')
@ -13,15 +12,14 @@ const all = require('it-all')
const delay = require('delay')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
connEncryption: [NOISE],
dht: KadDHT
},
config: {
@ -42,15 +40,21 @@ const createNode = async () => {
createNode()
])
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await Promise.all([
node1.dial(node2.peerInfo),
node2.dial(node3.peerInfo)
node1.dial(node2.peerId),
node2.dial(node3.peerId)
])
// Wait for onConnect handlers in the DHT
await delay(100)
const cid = new CID('QmTp9VkYvnHyrqKQuFPiuZkiX9gPcqj6x5LJ1rmWuSySnL')
await node1.contentRouting.provide(cid)
console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toBaseEncodedString())
// wait for propagation
await delay(300)

View File

@ -17,10 +17,13 @@ const Libp2p = require('libp2p')
const KadDHT = require('libp2p-kad-dht')
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [ TCP ],
streamMuxer: [ Mplex ],
connEncryption: [ SECIO ],
connEncryption: [ NOISE ],
// we add the DHT module that will enable Peer and Content Routing
dht: KadDHT
},
@ -40,18 +43,21 @@ const node1 = nodes[0]
const node2 = nodes[1]
const node3 = nodes[2]
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
await Promise.all([
node1.dial(node2.peerInfo),
node2.dial(node3.peerInfo)
node1.dial(node2.peerId),
node2.dial(node3.peerId)
])
// Set up of the cons might take time
await delay(100)
const peer = await node1.peerRouting.findPeer(node3.peerInfo.id)
const peer = await node1.peerRouting.findPeer(node3.peerId)
console.log('Found it, multiaddrs are:')
peer.multiaddrs.forEach((ma) => console.log(ma.toString()))
peer.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${peer.id.toB58String()}`))
```
You should see the output being something like:
@ -59,8 +65,8 @@ You should see the output being something like:
```Bash
> node 1.js
Found it, multiaddrs are:
/ip4/127.0.0.1/tcp/63617/p2p/QmWrFXvZr9S4iDqycyoyc2zDdrT1jg9wpdenUTdd1LTar6
/ip4/192.168.86.41/tcp/63617/p2p/QmWrFXvZr9S4iDqycyoyc2zDdrT1jg9wpdenUTdd1LTar6
/ip4/127.0.0.1/tcp/63617
/ip4/192.168.86.41/tcp/63617
```
You have successfully used Peer Routing to find a peer that you were not directly connected. Now all you have to do is to dial to the multiaddrs you discovered.
@ -75,7 +81,7 @@ Instead of calling `peerRouting.findPeer`, we will use `contentRouting.provide`
```JavaScript
await node1.contentRouting.provide(cid)
console.log('Node %s is providing %s', node1.peerInfo.id.toB58String(), cid.toBaseEncodedString())
console.log('Node %s is providing %s', node1.peerId.toB58String(), cid.toBaseEncodedString())
const provs = await all(node3.contentRouting.findProviders(cid, { timeout: 5000 }))

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 () => {
@ -29,7 +28,9 @@ generate(otherSwarmKey)
console.log('nodes started...')
await node1.dial(node2.peerInfo)
// Add node 2 data to node1's PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.dial(node2.peerId)
node2.handle('/private', ({ stream }) => {
pipe(
@ -42,7 +43,7 @@ generate(otherSwarmKey)
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/private')
const { stream } = await node1.dialProtocol(node2.peerId, '/private')
await pipe(
['This message is sent on a private network'],

View File

@ -3,33 +3,35 @@
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')
/**
* privateLibp2pNode returns a libp2p node function that will use the swarm
* key at the given `swarmKeyPath` to create the Protector
* 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 (swarmKeyPath) => {
const privateLibp2pNode = async (swarmKey) => {
const node = await Libp2p.create({
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP], // We're only using the TCP transport for this example
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: [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
peerDiscovery: [],
connProtector: new Protector(swarmKeyPath)
connProtector: new Protector(swarmKey)
}
})
node.peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
return node
}

View File

@ -1,19 +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-secio": "^0.12.1",
"libp2p-tcp": "^0.14.2"
}
}

View File

@ -3,21 +3,19 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const { NOISE } = require('libp2p-noise')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
}
})
@ -32,6 +30,9 @@ const createNode = async () => {
createNode()
])
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
// exact matching
node2.handle('/your-protocol', ({ stream }) => {
pipe(
@ -62,14 +63,14 @@ const createNode = async () => {
})
*/
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/your-protocol'])
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
await pipe(
['my own protocol, wow!'],
stream
)
/*
const { stream } = node1.dialProtocol(node2.peerInfo, ['/another-protocol/1.0.0'])
const { stream } = node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0'])
await pipe(
['my own protocol, wow!'],

View File

@ -3,21 +3,19 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const { NOISE } = require('libp2p-noise')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
}
})
@ -32,6 +30,9 @@ const createNode = async () => {
createNode()
])
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.handle(['/a', '/b'], ({ protocol, stream }) => {
pipe(
stream,
@ -43,19 +44,19 @@ const createNode = async () => {
)
})
const { stream: stream1 } = await node1.dialProtocol(node2.peerInfo, ['/a'])
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/a'])
await pipe(
['protocol (a)'],
stream1
)
const { stream: stream2 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
['protocol (b)'],
stream2
)
const { stream: stream3 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
['another stream on protocol (b)'],
stream3

View File

@ -4,21 +4,19 @@
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const { NOISE } = require('libp2p-noise')
const pipe = require('it-pipe')
const createNode = async () => {
const peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [MPLEX],
connEncryption: [SECIO]
connEncryption: [NOISE]
}
})
@ -32,6 +30,9 @@ const createNode = async () => {
createNode(),
createNode()
])
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node1.handle('/node-1', ({ stream }) => {
pipe(
@ -55,13 +56,13 @@ const createNode = async () => {
)
})
const { stream: stream1 } = await node1.dialProtocol(node2.peerInfo, ['/node-2'])
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2'])
await pipe(
['from 1 to 2'],
stream1
)
const { stream: stream2 } = await node2.dialProtocol(node1.peerInfo, ['/node-1'])
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
await pipe(
['from 2 to 1'],
stream2

View File

@ -6,7 +6,7 @@ The feature of agreeing on a protocol over an established connection is what we
# 1. Handle multiple protocols
Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `libp2p-tcp`, `peer-info`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js).
Let's see _protocol multiplexing_ in action! You will need the following modules for this example: `libp2p`, `libp2p-tcp`, `peer-id`, `it-pipe`, `it-buffer` and `streaming-iterables`. This example reuses the base left by the [Transports](../transports) example. You can see the complete solution at [1.js](./1.js).
After creating the nodes, we need to tell libp2p which protocols to handle.
@ -19,6 +19,9 @@ const { toBuffer } = require('it-buffer')
const node1 = nodes[0]
const node2 = nodes[1]
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
// Here we are telling libp2p that if someone dials this node to talk with the `/your-protocol`
// multicodec, the protocol identifier, please call this handler and give it the stream
// so that incomming data can be handled
@ -37,7 +40,7 @@ node2.handle('/your-protocol', ({ stream }) => {
After the protocol is _handled_, now we can dial to it.
```JavaScript
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/your-protocol'])
const { stream } = await node1.dialProtocol(node2.peerId, ['/your-protocol'])
await pipe(
['my own protocol, wow!'],
@ -59,7 +62,7 @@ node2.handle('/another-protocol/1.0.1', ({ stream }) => {
)
})
// ...
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/another-protocol/1.0.0'])
const { stream } = await node1.dialProtocol(node2.peerId, ['/another-protocol/1.0.0'])
await pipe(
['my own protocol, wow!'],
@ -128,19 +131,19 @@ node2.handle(['/a', '/b'], ({ protocol, stream }) => {
)
})
const { stream } = await node1.dialProtocol(node2.peerInfo, ['/a'])
const { stream } = await node1.dialProtocol(node2.peerId, ['/a'])
await pipe(
['protocol (a)'],
stream
)
const { stream: stream2 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
const { stream: stream2 } = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
['protocol (b)'],
stream2
)
const { stream: stream3 } = await node1.dialProtocol(node2.peerInfo, ['/b'])
const { stream: stream3 } = await node1.dialProtocol(node2.peerId, ['/b'])
await pipe(
['another stream on protocol (b)'],
stream3

View File

@ -1,24 +1,23 @@
/* eslint-disable no-console */
'use strict'
const { Buffer } = require('buffer')
const Libp2p = require('../../')
const TCP = require('libp2p-tcp')
const Mplex = require('libp2p-mplex')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
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 peerInfo = await PeerInfo.create()
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: ['/ip4/0.0.0.0/tcp/0']
},
modules: {
transport: [TCP],
streamMuxer: [Mplex],
connEncryption: [SECIO],
connEncryption: [NOISE],
pubsub: Gossipsub
}
})
@ -35,18 +34,22 @@ const createNode = async () => {
createNode()
])
await node1.dial(node2.peerInfo)
// Add node's 2 data to the PeerStore
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

@ -1,6 +1,6 @@
# Publish Subscribe
Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [libp2p-gossipsub](https://github.com/ChainSafe/gossipsub-js), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
Publish Subscribe is also included on the stack. Currently, we have two PubSub implementation available [libp2p-floodsub](https://github.com/libp2p/js-libp2p-floodsub) and [libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub), with many more being researched at [research-pubsub](https://github.com/libp2p/research-pubsub).
We've seen many interesting use cases appear with this, here are some highlights:
@ -21,10 +21,13 @@ 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: [ SECIO ],
connEncryption: [ NOISE ],
// we add the Pubsub module we want
pubsub: Gossipsub
}
@ -39,19 +42,24 @@ const topic = 'news'
const node1 = nodes[0]
const node2 = nodes[1]
await node1.dial(node2.peerInfo)
// Add node's 2 data to the PeerStore
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
await node1.pubsub.subscribe(topic, (msg) => {
console.log(`node1 received: ${msg.data.toString()}`)
})
await node1.dial(node2.peerId)
await node2.pubsub.subscribe(topic, (msg) => {
console.log(`node2 received: ${msg.data.toString()}`)
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)
// 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

@ -3,19 +3,18 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const SECIO = require('libp2p-secio')
const PeerInfo = require('peer-info')
const createNode = async (peerInfo) => {
// To signall the addresses we want to be available, we use
// the multiaddr format, a self describable address
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const { NOISE } = require('libp2p-noise')
const createNode = async () => {
const node = await Libp2p.create({
peerInfo,
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: [SECIO]
connEncryption: [NOISE]
}
})
@ -24,10 +23,9 @@ const createNode = async (peerInfo) => {
}
;(async () => {
const peerInfo = await PeerInfo.create()
const node = await createNode(peerInfo)
const node = await createNode()
console.log('node has started (true/false):', node.isStarted())
console.log('listening on:')
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
})();

View File

@ -3,23 +3,22 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe')
const concat = require('it-concat')
const createNode = async (peerInfo) => {
// To signall the addresses we want to be available, we use
// the multiaddr format, a self describable address
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const createNode = async () => {
const node = await Libp2p.create({
peerInfo,
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: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -30,17 +29,13 @@ const createNode = async (peerInfo) => {
function printAddrs (node, number) {
console.log('node %s is listening on:', number)
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
}
;(async () => {
const [peerInfo1, peerInfo2] = await Promise.all([
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2] = await Promise.all([
createNode(peerInfo1),
createNode(peerInfo2)
createNode(),
createNode()
])
printAddrs(node1, '1')
@ -54,7 +49,8 @@ function printAddrs (node, number) {
console.log(result.toString())
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'],

View File

@ -4,24 +4,23 @@
const Libp2p = require('../..')
const TCP = require('libp2p-tcp')
const WebSockets = require('libp2p-websockets')
const SECIO = require('libp2p-secio')
const { NOISE } = require('libp2p-noise')
const MPLEX = require('libp2p-mplex')
const PeerInfo = require('peer-info')
const pipe = require('it-pipe')
const createNode = async (peerInfo, transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
const createNode = async (transports, addresses = []) => {
if (!Array.isArray(addresses)) {
addresses = [addresses]
}
multiaddrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: addresses
},
modules: {
transport: transports,
connEncryption: [SECIO],
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -32,7 +31,7 @@ const createNode = async (peerInfo, transports, multiaddrs = []) => {
function printAddrs(node, number) {
console.log('node %s is listening on:', number)
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
}
function print ({ stream }) {
@ -47,15 +46,10 @@ function print ({ stream }) {
}
;(async () => {
const [peerInfo1, peerInfo2, peerInfo3] = await Promise.all([
PeerInfo.create(),
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2, node3] = await Promise.all([
createNode(peerInfo1, [TCP], '/ip4/0.0.0.0/tcp/0'),
createNode(peerInfo2, [TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode(peerInfo3, [WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
createNode([TCP], '/ip4/0.0.0.0/tcp/0'),
createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
])
printAddrs(node1, '1')
@ -66,15 +60,19 @@ function print ({ stream }) {
node2.handle('/print', print)
node3.handle('/print', print)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs)
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['node 1 dialed to node 2 successfully'],
stream
)
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerInfo, '/print')
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print')
await pipe(
['node 2 dialed to node 3 successfully'],
stream2
@ -82,7 +80,7 @@ function print ({ stream }) {
// node 3 (listening WebSockets) can dial node 1 (TCP)
try {
await node3.dialProtocol(node1.peerInfo, '/print')
await node3.dialProtocol(node1.peerId, '/print')
} catch (err) {
console.log('node 3 failed to dial to node 1 with:', err.message)
}

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:
@ -25,18 +25,18 @@ First thing is to create our own libp2p node! Insert:
const Libp2p = require('libp2p')
const TCP = require('libp2p-tcp')
const SECIO = require('libp2p-secio')
const createNode = async (peerInfo) => {
// To signall the addresses we want to be available, we use
// the multiaddr format, a self describable address
peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/0')
const { NOISE } = require('libp2p-noise')
const createNode = async () => {
const node = await Libp2p.create({
peerInfo,
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: [ SECIO ]
connEncryption: [ NOISE ]
}
})
@ -48,8 +48,7 @@ const createNode = async (peerInfo) => {
Now that we have a function to create our own libp2p node, let's create a node with it.
```JavaScript
const peerInfo = await PeerInfo.create()
const node = await createNode(peerInfo)
const node = await createNode()
// At this point the node has started
console.log('node has started (true/false):', node.isStarted())
@ -59,7 +58,7 @@ console.log('node has started (true/false):', node.isStarted())
// 0, which means "listen in any network interface and pick
// a port for me
console.log('listening on:')
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
```
Running this should result in something like:
@ -78,36 +77,53 @@ 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) {
console.log('node %s is listening on:', number)
node.peerInfo.multiaddrs.forEach((ma) => console.log(ma.toString()))
node.multiaddrs.forEach((ma) => console.log(`${ma.toString()}/p2p/${node.peerId.toB58String()}`))
}
```
Then,
Then add,
```js
;(async () => {
const [peerInfo1, peerInfo2] = await Promise.all([
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2] = await Promise.all([
createNode(),
createNode()
@ -116,18 +132,16 @@ 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())
})
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['Hello', ' ', 'p2p', ' ', 'world', '!'],
@ -135,8 +149,9 @@ Then,
)
})();
```
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
@ -151,33 +166,34 @@ 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 (peerInfo, transports, multiaddrs = []) => {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
const createNode = async (transports, addresses = []) => {
if (!Array.isArray(addresses)) {
addresses = [addresses]
}
multiaddrs.forEach((addr) => peerInfo.multiaddrs.add(addr))
const node = await Libp2p.create({
peerInfo,
addresses: {
listen: addresses
},
modules: {
transport: transports,
connEncryption: [ SECIO ]
connEncryption: [NOISE],
streamMuxer: [MPLEX]
}
})
@ -194,15 +210,10 @@ Let's update our flow to create nodes and see how they behave when dialing to ea
const WebSockets = require('libp2p-websockets')
const TCP = require('libp2p-tcp')
const [peerInfo1, peerInfo2, peerInfo3] = await Promise.all([
PeerInfo.create(),
PeerInfo.create(),
PeerInfo.create()
])
const [node1, node2, node3] = await Promise.all([
createNode(peerInfo1, [TCP], '/ip4/0.0.0.0/tcp/0'),
createNode(peerInfo2, [TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode(peerInfo3, [WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
createNode([TCP], '/ip4/0.0.0.0/tcp/0'),
createNode([TCP, WebSockets], ['/ip4/0.0.0.0/tcp/0', '/ip4/127.0.0.1/tcp/10000/ws']),
createNode([WebSockets], '/ip4/127.0.0.1/tcp/20000/ws')
])
printAddrs(node1, '1')
@ -213,15 +224,19 @@ node1.handle('/print', print)
node2.handle('/print', print)
node3.handle('/print', print)
node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
node2.peerStore.addressBook.set(node3.peerId, node3.multiaddrs)
node3.peerStore.addressBook.set(node1.peerId, node1.multiaddrs)
// node 1 (TCP) dials to node 2 (TCP+WebSockets)
const { stream } = await node1.dialProtocol(node2.peerInfo, '/print')
const { stream } = await node1.dialProtocol(node2.peerId, '/print')
await pipe(
['node 1 dialed to node 2 successfully'],
stream
)
// node 2 (TCP+WebSockets) dials to node 2 (WebSockets)
const { stream: stream2 } = await node2.dialProtocol(node3.peerInfo, '/print')
const { stream: stream2 } = await node2.dialProtocol(node3.peerId, '/print')
await pipe(
['node 2 dialed to node 3 successfully'],
stream2
@ -229,13 +244,13 @@ await pipe(
// node 3 (WebSockets) attempts to dial to node 1 (TCP)
try {
await node3.dialProtocol(node1.peerInfo, '/print')
await node3.dialProtocol(node1.peerId, '/print')
} catch (err) {
console.log('node 3 failed to dial to node 1 with:', err.message)
}
```
`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 }) {
@ -250,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
@ -269,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

@ -22,6 +22,7 @@
["libp2p/js-libp2p-websockets", "libp2p-websockets"],
"secure channels",
["NodeFactoryIo/js-libp2p-noise", "libp2p-noise"],
["libp2p/js-libp2p-secio", "libp2p-secio"],
"stream multiplexers",
@ -32,7 +33,7 @@
["libp2p/js-libp2p-kad-dht", "libp2p-kad-dht"],
["libp2p/js-libp2p-mdns", "libp2p-mdns"],
["libp2p/js-libp2p-webrtc-star", "libp2p-webrtc-star"],
["ChainSafe/discv5", "discv5"],
["ChainSafe/discv5", "@chainsafe/discv5"],
"content routing",
["libp2p/js-libp2p-delegated-content-routing", "libp2p-delegated-content-routing"],
@ -48,12 +49,10 @@
"data types",
["libp2p/js-peer-id", "peer-id"],
["libp2p/js-peer-info", "peer-info"],
"pubsub",
["libp2p/js-libp2p-pubsub", "libp2p-pubsub"],
["libp2p/js-libp2p-floodsub", "libp2p-floodsub"],
["ChainSafe/gossipsub-js", "libp2p-gossipsub"],
["ChainSafe/js-libp2p-gossipsub", "libp2p-gossipsub"],
"extensions",
["libp2p/js-libp2p-nat-mgnr", "libp2p-nat-mgnr"],

View File

@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.27.8",
"version": "0.29.4",
"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,6 +50,7 @@
"err-code": "^2.0.0",
"events": "^3.1.0",
"hashlru": "^2.3.0",
"interface-datastore": "^2.0.0",
"ipfs-utils": "^2.2.0",
"it-all": "^1.0.1",
"it-buffer": "^0.1.2",
@ -57,97 +58,118 @@
"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.2.8",
"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.1.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",
"peer-info": "^0.17.0",
"protons": "^1.0.1",
"peer-id": "^0.14.2",
"protons": "^2.0.0",
"retimer": "^2.0.0",
"streaming-iterables": "^4.1.0",
"timeout-abort-controller": "^1.0.0",
"sanitize-filename": "^1.6.3",
"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": "^21.10.1",
"aegir": "^27.0.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"cids": "^0.8.0",
"chai-bytes": "^0.1.2",
"chai-string": "^1.5.0",
"cids": "^1.0.0",
"delay": "^4.3.0",
"dirty-chai": "^2.0.1",
"interop-libp2p": "~0.0.1",
"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",
"libp2p-bootstrap": "^0.10.3",
"libp2p-delegated-content-routing": "^0.4.5",
"libp2p-delegated-peer-routing": "^0.4.3",
"libp2p-floodsub": "^0.20.0",
"libp2p-gossipsub": "^0.2.6",
"libp2p-kad-dht": "^0.18.6",
"libp2p-mdns": "^0.13.0",
"libp2p-mplex": "^0.9.5",
"libp2p-secio": "^0.12.4",
"libp2p-tcp": "^0.14.1",
"libp2p-webrtc-star": "^0.17.9",
"libp2p-websockets": "^0.13.1",
"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",
"sinon": "^9.0.2"
"promisify-es6": "^1.0.3",
"rimraf": "^3.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>",
"Maciej Krüger <mkg20001@gmail.com>",
"Alex Potsides <alex@achingbrain.net>",
"Hugo Dias <mail@hugodias.me>",
"dirkmc <dirkmdev@gmail.com>",
"Volker Mische <volker.mische@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"Cayman <caymannava@gmail.com>",
"a1300 <matthias-knopp@gmx.net>",
"Elven <mon.samuel@qq.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"Giovanni T. Parra <fiatjaf@gmail.com>",
"Ryan Bell <ryan@piing.net>",
"Thomas Eizinger <thomas@eizinger.io>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Didrik Nordström <didrik@betamos.se>",
"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>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Samlior <samlior@foxmail.com>",
"Smite Chow <xiaopengyou@live.com>",
"Soeren <nikorpoulsen@gmail.com>",
"Sönke Hahn <soenkehahn@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"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>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Francis Gulotta <wizard@roborooter.com>",
"Felipe Martins <felipebrasil93@gmail.com>",
"isan_rivkin <isanrivkin@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Irakli Gozalishvili <rfobic@gmail.com>",
"Henrique Dias <hacdias@gmail.com>"
"Henrique Dias <hacdias@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>"
]
}

View File

@ -0,0 +1,49 @@
# Address Manager
The Address manager is responsible for keeping an updated register of the peer's addresses. It includes 3 different types of Addresses: `Listen Addresses`, `Announce Addresses` and `No Announce Addresses`.
These Addresses should be specified in your libp2p [configuration](../../doc/CONFIGURATION.md) when you create your node.
## Listen Addresses
A libp2p node should have a set of listen addresses, which will be used by libp2p underlying transports to listen for dials from other nodes in the network.
Before a libp2p node starts, its configured listen addresses will be passed to the AddressManager, so that during startup the libp2p transports can use them to listen for connections. Accordingly, listen addresses should be specified through the libp2p configuration, in order to have the `AddressManager` created with them.
It is important pointing out that libp2p accepts ephemeral listening addresses. In this context, the provided listen addresses might not be exactly the same as the ones used by the transports. For example TCP may replace `/ip4/0.0.0.0/tcp/0` with something like `/ip4/127.0.0.1/tcp/8989`. As a consequence, libp2p should take into account this when determining its advertised addresses.
## Announce Addresses
In some scenarios, a libp2p node will need to announce addresses that it is not listening on. In other words, Announce Addresses are an amendment to the Listen Addresses that aim to enable other nodes to achieve connectivity to this node.
Scenarios for Announce Addresses include:
- when you setup a libp2p node in your private network at home, but you need to announce your public IP Address to the outside world;
- when you want to announce a DNS address, which maps to your public IP Address.
## No Announce Addresses
While we need to add Announce Addresses to enable peers' connectivity, we should also avoid announcing addresses that will not be reachable. No Announce Addresses should be specified so that they are filtered from the advertised multiaddrs.
As stated in the Listen Addresses section, Listen Addresses might be modified by libp2p transports after the successfully bind to those addresses. Libp2p should also take these changes into account so that they can be matched when No Announce Addresses are being filtered out of the advertised multiaddrs.
## Implementation
When a libp2p node is created, the Address Manager will be populated from the provided addresses through the libp2p configuration. Once the node is started, the Transport Manager component will gather the listen addresses from the Address Manager, so that the libp2p transports can attempt to bind to them.
Libp2p will use the the Address Manager as the source of truth when advertising the peers addresses. After all transports are ready, other libp2p components/subsystems will kickoff, namely the Identify Service and the DHT. Both of them will announce the node addresses to the other peers in the network. The announce and noAnnounce addresses will have an important role here and will be gathered by libp2p to compute its current addresses to advertise everytime it is needed.
## Future Considerations
### Dynamic address modifications
In a future iteration, we can enable these addresses to be modified in runtime. For this, the Address Manager should be responsible for notifying interested subsystems of these changes, through an Event Emitter.
#### Modify Listen Addresses
While adding new addresses to listen on runtime should be trivial, removing a listen address might have bad implications for the node, since all the connections using that listen address will be closed. However, libp2p should provide a mechanism for both adding and removing listen addresses in the future.
Every time a new listen address is added, the Address Manager should emit an event with the new multiaddrs to listen. The Transport Manager should listen to this events and act accordingly.
#### Modify Announce Addresses
When the announce addresses are modified, the Address Manager should emit an event so that other subsystems can act accordingly. For example, libp2p identify service should use the libp2p push protocol to inform other peers about these changes.

View File

@ -0,0 +1,58 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:addresses')
log.error = debug('libp2p:addresses:error')
const multiaddr = require('multiaddr')
/**
* Responsible for managing this peers addresses.
* Peers can specify their listen, announce and noAnnounce addresses.
* The listen addresses will be used by the libp2p transports to listen for new connections,
* while the announce an noAnnounce addresses will be combined with the listen addresses for
* address adverstising to other peers in the network.
*/
class AddressManager {
/**
* @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.
*/
constructor ({ listen = [], announce = [], noAnnounce = [] } = {}) {
this.listen = new Set(listen)
this.announce = new Set(announce)
this.noAnnounce = new Set(noAnnounce)
}
/**
* Get peer listen multiaddrs.
*
* @returns {Array<Multiaddr>}
*/
getListenAddrs () {
return Array.from(this.listen).map((a) => multiaddr(a))
}
/**
* Get peer announcing multiaddrs.
*
* @returns {Array<Multiaddr>}
*/
getAnnounceAddrs () {
return Array.from(this.announce).map((a) => multiaddr(a))
}
/**
* Get peer noAnnouncing multiaddrs.
*
* @returns {Array<Multiaddr>}
*/
getNoAnnounceAddrs () {
return Array.from(this.noAnnounce).map((a) => multiaddr(a))
}
}
module.exports = AddressManager

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

@ -1,7 +1,9 @@
'use strict'
const debug = require('debug')
const PeerInfo = require('peer-info')
const log = debug('libp2p:circuit:hop')
log.error = debug('libp2p:circuit:hop:error')
const PeerId = require('peer-id')
const { validateAddrs } = require('./utils')
const StreamHandler = require('./stream-handler')
@ -14,9 +16,6 @@ const { stop } = require('./stop')
const multicodec = require('./../multicodec')
const log = debug('libp2p:circuit:hop')
log.error = debug('libp2p:circuit:hop:error')
module.exports.handleHop = async function handleHop ({
connection,
request,
@ -42,7 +41,7 @@ module.exports.handleHop = async function handleHop ({
// Get the connection to the destination (stop) peer
const destinationPeer = new PeerId(request.dstPeer.id)
const destinationConnection = circuit._registrar.getConnection(new PeerInfo(destinationPeer))
const destinationConnection = circuit._connectionManager.get(destinationPeer)
if (!destinationConnection && !circuit._options.hop.active) {
log('HOP request received but we are not connected to the destination peer')
return streamHandler.end({
@ -91,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 ({
@ -120,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

@ -3,7 +3,6 @@
const mafmt = require('mafmt')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const withIs = require('class-is')
const { CircuitRelay: CircuitPB } = require('./protocol')
@ -22,7 +21,7 @@ class Circuit {
/**
* Creates an instance of Circuit.
*
* @constructor
* @class
* @param {object} options
* @param {Libp2p} options.libp2p
* @param {Upgrader} options.upgrader
@ -30,9 +29,11 @@ class Circuit {
constructor ({ libp2p, upgrader }) {
this._dialer = libp2p.dialer
this._registrar = libp2p.registrar
this._connectionManager = libp2p.connectionManager
this._upgrader = upgrader
this._options = libp2p._config.relay
this.peerInfo = libp2p.peerInfo
this._libp2p = libp2p
this.peerId = libp2p.peerId
this._registrar.handle(multicodec, this._onProtocol.bind(this))
}
@ -107,7 +108,7 @@ class Circuit {
const destinationPeer = PeerId.createFromCID(destinationAddr.getPeerId())
let disconnectOnFailure = false
let relayConnection = this._registrar.getConnection(new PeerInfo(relayPeer))
let relayConnection = this._connectionManager.get(relayPeer)
if (!relayConnection) {
relayConnection = await this._dialer.connectToPeer(relayAddr, options)
disconnectOnFailure = true
@ -120,17 +121,17 @@ class Circuit {
request: {
type: CircuitPB.Type.HOP,
srcPeer: {
id: this.peerInfo.id.toBytes(),
addrs: this.peerInfo.multiaddrs.toArray().map(addr => addr.buffer)
id: this.peerId.toBytes(),
addrs: this._libp2p.multiaddrs.map(addr => addr.bytes)
},
dstPeer: {
id: destinationPeer.toBytes(),
addrs: [multiaddr(destinationAddr).buffer]
addrs: [multiaddr(destinationAddr).bytes]
}
}
})
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerInfo.id.toB58String()}`)
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.peerId.toB58String()}`)
const maConn = toConnection({
stream: virtualConnection,
remoteAddr: ma,
@ -151,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

@ -1,20 +1,43 @@
'use strict'
const mergeOptions = require('merge-options')
const { dnsaddrResolver } = require('multiaddr/src/resolvers')
const Constants = require('./constants')
const { AGENT_VERSION } = require('./identify/consts')
const { FaultTolerance } = require('./transport-manager')
const DefaultConfig = {
addresses: {
listen: [],
announce: [],
noAnnounce: []
},
connectionManager: {
minPeers: 25
minConnections: 25
},
transportManager: {
faultTolerance: FaultTolerance.FATAL_ALL
},
dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
dialTimeout: Constants.DIAL_TIMEOUT
dialTimeout: Constants.DIAL_TIMEOUT,
resolvers: {
dnsaddr: dnsaddrResolver
}
},
host: {
agentVersion: AGENT_VERSION
},
metrics: {
enabled: false
},
peerStore: {
persistence: false,
threshold: 5
},
config: {
dht: {
enabled: false,

View File

@ -1,11 +1,18 @@
'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')
const PeerId = require('peer-id')
const {
ERR_INVALID_PARAMETERS
} = require('../errors')
@ -18,42 +25,76 @@ const defaultOptions = {
maxReceivedData: Infinity,
maxEventLoopDelay: Infinity,
pollInterval: 2000,
autoDialInterval: 10000,
movingAverageInterval: 60000,
defaultPeerValue: 1
}
class ConnectionManager {
/**
* 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()
this._libp2p = libp2p
this._registrar = libp2p.registrar
this._peerId = libp2p.peerInfo.id.toB58String()
this._peerId = libp2p.peerId.toB58String()
this._options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, options)
if (this._options.maxConnections < this._options.minConnections) {
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._metrics = libp2p.metrics
this._libp2p = libp2p
/**
* Map of peer identifiers to their peer value for pruning connections.
*
* @type {Map<string, number>}
*/
this._peerValues = new Map()
this._connections = 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)
}
/**
* Get current number of open connections.
*/
get size () {
return Array.from(this.connections.values())
.reduce((accumulator, value) => accumulator + value.length, 0)
}
/**
@ -61,7 +102,7 @@ class ConnectionManager {
* only event loop and connection limits will be monitored.
*/
start () {
if (this._metrics) {
if (this._libp2p.metrics) {
this._timer = this._timer || retimer(this._checkMetrics, this._options.pollInterval)
}
@ -72,23 +113,52 @@ class ConnectionManager {
})
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
*/
stop () {
async stop () {
this._autoDialTimeout && this._autoDialTimeout.clear()
this._timer && this._timer.clear()
this._latencyMonitor && this._latencyMonitor.removeListener('data', this._onLatencyMeasure)
debug('stopped')
this._started = false
await this._close()
log('stopped')
}
/**
* Cleans up the connections
*
* @async
*/
async _close () {
// Close all connections we're tracking
const tasks = []
for (const connectionList of this.connections.values()) {
for (const connection of connectionList) {
tasks.push(connection.close())
}
}
await tasks
this.connections.clear()
}
/**
* 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) {
@ -103,83 +173,192 @@ class ConnectionManager {
/**
* Checks the libp2p metrics to determine if any values have exceeded
* the configured maximums.
*
* @private
*/
_checkMetrics () {
const movingAverages = this._metrics.global.movingAverages
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) {
const peerId = connection.remotePeer.toB58String()
this._connections.set(connection.id, connection)
if (!this._peerValues.has(peerId)) {
this._peerValues.set(peerId, this._options.defaultPeerValue)
const peerId = connection.remotePeer
const peerIdStr = peerId.toB58String()
const storedConn = this.connections.get(peerIdStr)
this.emit('peer:connect', connection)
if (storedConn) {
storedConn.push(connection)
} else {
this.connections.set(peerIdStr, [connection])
}
this._checkLimit('maxConnections', this._connections.size)
this._libp2p.peerStore.keyBook.set(peerId, peerId.pubKey)
if (!this._peerValues.has(peerIdStr)) {
this._peerValues.set(peerIdStr, this._options.defaultPeerValue)
}
this._checkMaxLimit('maxConnections', this.size)
}
/**
* Removes the connection from tracking
*
* @param {Connection} connection
*/
onDisconnect (connection) {
this._connections.delete(connection.id)
this._peerValues.delete(connection.remotePeer.toB58String())
const peerId = connection.remotePeer.toB58String()
let storedConn = this.connections.get(peerId)
if (storedConn && storedConn.length > 1) {
storedConn = storedConn.filter((conn) => conn.id !== connection.id)
this.connections.set(peerId, storedConn)
} else if (storedConn) {
this.connections.delete(peerId)
this._peerValues.delete(connection.remotePeer.toB58String())
this.emit('peer:disconnect', connection)
}
}
/**
* Get a connection with a peer.
*
* @param {PeerId} peerId
* @returns {Connection}
*/
get (peerId) {
const connections = this.getAll(peerId)
if (connections.length) {
return connections[0]
}
return null
}
/**
* Get all open connections with a peer.
*
* @param {PeerId} peerId
* @returns {Array<Connection>}
*/
getAll (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const connections = this.connections.get(id)
// Return all open connections
if (connections) {
return connections.filter(connection => connection.stat.status === 'open')
}
return []
}
/**
* 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) {
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)
for (const connection of this._connections.values()) {
if (connection.remotePeer.toB58String() === peerId) {
connection.close()
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()
break
}
}

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,11 +20,11 @@ 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<PeerInfo>}
* @returns {AsyncIterable<{ id: PeerId, multiaddrs: Multiaddr[] }>}
*/
async * findProviders (key, options) {
if (!routers.length) {
@ -42,8 +42,8 @@ module.exports = (node) => {
})
)
for (const pInfo of result) {
yield pInfo
for (const peer of result) {
yield peer
}
},
@ -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

@ -4,12 +4,12 @@ const multiaddr = require('multiaddr')
const errCode = require('err-code')
const TimeoutController = require('timeout-abort-controller')
const anySignal = require('any-signal')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const debug = require('debug')
const log = debug('libp2p:dialer')
log.error = debug('libp2p:dialer:error')
const { DialRequest } = require('./dial-request')
const getPeer = require('../get-peer')
const { codes } = require('../errors')
const {
@ -20,19 +20,22 @@ 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.
* @param {object} [options.resolvers = {}] - multiaddr resolvers to use when dialing
*/
constructor ({
transportManager,
peerStore,
concurrency = MAX_PARALLEL_DIALS,
timeout = DIAL_TIMEOUT,
perPeerLimit = MAX_PER_PEER_DIALS
perPeerLimit = MAX_PER_PEER_DIALS,
resolvers = {}
}) {
this.transportManager = transportManager
this.peerStore = peerStore
@ -41,6 +44,10 @@ class Dialer {
this.perPeerLimit = perPeerLimit
this.tokens = [...new Array(concurrency)].map((_, index) => index)
this._pendingDials = new Map()
for (const [key, value] of Object.entries(resolvers)) {
multiaddr.resolvers.set(key, value)
}
}
/**
@ -58,18 +65,19 @@ class Dialer {
}
/**
* Connects to a given `PeerId` or `Multiaddr` by dialing all of its known addresses.
* Connects to a given `peer` by dialing all of its known addresses.
* The dial to the first address that is successfully able to upgrade a connection
* will be used.
*
* @param {PeerInfo|Multiaddr} 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 = {}) {
const dialTarget = this._createDialTarget(peer)
if (dialTarget.addrs.length === 0) {
const dialTarget = await this._createDialTarget(peer)
if (!dialTarget.addrs.length) {
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
}
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
@ -99,21 +107,36 @@ 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 {PeerInfo|Multiaddr} peer A PeerId or Multiaddr
* @returns {DialTarget}
* @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr
* @returns {Promise<DialTarget>}
*/
_createDialTarget (peer) {
const dialable = Dialer.getDialable(peer)
if (multiaddr.isMultiaddr(dialable)) {
return {
id: dialable.toString(),
addrs: [dialable]
}
async _createDialTarget (peer) {
const { id, multiaddrs } = getPeer(peer)
if (multiaddrs) {
this.peerStore.addressBook.add(id, multiaddrs)
}
const addrs = this.peerStore.multiaddrsForPeer(dialable)
let knownAddrs = 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.
if (multiaddr.isMultiaddr(peer)) {
knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr))
knownAddrs.unshift(peer)
}
const addrs = []
for (const a of knownAddrs) {
const resolvedAddrs = await this._resolve(a)
resolvedAddrs.forEach(ra => addrs.push(ra))
}
return {
id: dialable.id.toB58String(),
id: id.toB58String(),
addrs
}
}
@ -128,10 +151,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) {
@ -180,33 +204,49 @@ class Dialer {
}
/**
* Converts the given `peer` into a `PeerInfo` or `Multiaddr`.
* @static
* @param {PeerInfo|PeerId|Multiaddr|string} peer
* @returns {PeerInfo|Multiaddr}
* Resolve multiaddr recursively.
*
* @param {Multiaddr} ma
* @returns {Promise<Array<Multiaddr>>}
*/
static getDialable (peer) {
if (PeerInfo.isPeerInfo(peer)) return peer
if (typeof peer === 'string') {
peer = multiaddr(peer)
async _resolve (ma) {
// TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place
// Now only supporting resolve for dnsaddr
const resolvableProto = ma.protoNames().includes('dnsaddr')
// Multiaddr is not resolvable? End recursion!
if (!resolvableProto) {
return [ma]
}
let addr
if (multiaddr.isMultiaddr(peer)) {
addr = peer
try {
peer = PeerId.createFromCID(peer.getPeerId())
} catch (err) {
throw errCode(new Error('The multiaddr did not contain a valid peer id'), codes.ERR_INVALID_PEER)
const resolvedMultiaddrs = await this._resolveRecord(ma)
const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map((nm) => {
return this._resolve(nm)
}))
return recursiveMultiaddrs.flat().reduce((array, newM) => {
if (!array.find(m => m.equals(newM))) {
array.push(newM)
}
}
return array
}, []) // Unique addresses
}
if (PeerId.isPeerId(peer)) {
peer = new PeerInfo(peer)
/**
* Resolve a given multiaddr. If this fails, an empty array will be returned
*
* @param {Multiaddr} ma
* @returns {Promise<Array<Multiaddr>>}
*/
async _resolveRecord (ma) {
try {
ma = multiaddr(ma.toString()) // Use current multiaddr module
const multiaddrs = await ma.resolve()
return multiaddrs
} catch (_) {
log.error(`multiaddr ${ma} could not be resolved`)
return []
}
addr && peer.multiaddrs.add(addr)
return peer
}
}

View File

@ -2,18 +2,21 @@
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',
ERR_ALREADY_ABORTED: 'ERR_ALREADY_ABORTED',
ERR_NO_VALID_ADDRESSES: 'ERR_NO_VALID_ADDRESSES',
ERR_DIALED_SELF: 'ERR_DIALED_SELF',
ERR_DISCOVERED_SELF: 'ERR_DISCOVERED_SELF',
ERR_DUPLICATE_TRANSPORT: 'ERR_DUPLICATE_TRANSPORT',
ERR_ENCRYPTION_FAILED: 'ERR_ENCRYPTION_FAILED',
@ -26,5 +29,7 @@ exports.codes = {
ERR_TIMEOUT: 'ERR_TIMEOUT',
ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE',
ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED',
ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL'
ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL',
ERR_INVALID_MULTIADDR: 'ERR_INVALID_MULTIADDR',
ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID'
}

View File

@ -1,73 +0,0 @@
'use strict'
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const multiaddr = require('multiaddr')
const errCode = require('err-code')
/**
* Converts the given `peer` to a `PeerInfo` instance.
* The `PeerStore` will be checked for the resulting peer, and
* the peer will be updated in the `PeerStore`.
*
* @param {PeerInfo|PeerId|Multiaddr|string} peer
* @param {PeerStore} peerStore
* @returns {PeerInfo}
*/
function getPeerInfo (peer, peerStore) {
if (typeof peer === 'string') {
peer = multiaddr(peer)
}
let addr
if (multiaddr.isMultiaddr(peer)) {
addr = peer
try {
peer = PeerId.createFromB58String(peer.getPeerId())
} catch (err) {
throw errCode(
new Error(`${peer} is not a valid peer type`),
'ERR_INVALID_MULTIADDR'
)
}
}
if (PeerId.isPeerId(peer)) {
peer = new PeerInfo(peer)
}
addr && peer.multiaddrs.add(addr)
return peerStore ? peerStore.put(peer) : peer
}
/**
* If `getPeerInfo` does not return a peer with multiaddrs,
* the `libp2p` PeerRouter will be used to attempt to find the peer.
*
* @async
* @param {PeerInfo|PeerId|Multiaddr|string} peer
* @param {Libp2p} libp2p
* @returns {Promise<PeerInfo>}
*/
function getPeerInfoRemote (peer, libp2p) {
let peerInfo
try {
peerInfo = getPeerInfo(peer, libp2p.peerStore)
} catch (err) {
throw errCode(err, 'ERR_INVALID_PEER_TYPE')
}
// If we don't have an address for the peer, attempt to find it
if (peerInfo.multiaddrs.size < 1) {
return libp2p.peerRouting.findPeer(peerInfo.id)
}
return peerInfo
}
module.exports = {
getPeerInfoRemote,
getPeerInfo
}

40
src/get-peer.js Normal file
View File

@ -0,0 +1,40 @@
'use strict'
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
const errCode = require('err-code')
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
* @returns {{ id: PeerId, multiaddrs: Array<Multiaddr> }}
*/
function getPeer (peer) {
if (typeof peer === 'string') {
peer = multiaddr(peer)
}
let addr
if (multiaddr.isMultiaddr(peer)) {
addr = peer
try {
peer = PeerId.createFromB58String(peer.getPeerId())
} catch (err) {
throw errCode(
new Error(`${peer} is not a valid peer type`),
codes.ERR_INVALID_MULTIADDR
)
}
}
return {
id: peer,
multiaddrs: addr ? [addr] : undefined
}
}
module.exports = getPeer

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,21 +1,24 @@
'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 PeerInfo = require('peer-info')
const PeerId = require('peer-id')
const multiaddr = require('multiaddr')
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,
@ -24,46 +27,13 @@ const {
PROTOCOL_VERSION
} = require('./consts')
const errCode = require('err-code')
const { codes } = require('../errors')
class IdentifyService {
/**
* Replaces the multiaddrs on the given `peerInfo`,
* with the provided `multiaddrs`
* @param {PeerInfo} peerInfo
* @param {Array<Multiaddr>|Array<Buffer>} multiaddrs
*/
static updatePeerAddresses (peerInfo, multiaddrs) {
if (multiaddrs && multiaddrs.length > 0) {
peerInfo.multiaddrs.clear()
multiaddrs.forEach(ma => {
try {
peerInfo.multiaddrs.add(ma)
} catch (err) {
log.error('could not add multiaddr', err)
}
})
}
}
/**
* Replaces the protocols on the given `peerInfo`,
* with the provided `protocols`
* @static
* @param {PeerInfo} peerInfo
* @param {Array<string>} protocols
*/
static updatePeerProtocols (peerInfo, protocols) {
if (protocols && protocols.length > 0) {
peerInfo.protocols.clear()
protocols.forEach(proto => peerInfo.protocols.add(proto))
}
}
/**
* 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) {
@ -78,41 +48,73 @@ class IdentifyService {
}
/**
* @constructor
* @class
* @param {object} options
* @param {Registrar} options.registrar
* @param {Map<string, handler>} options.protocols A reference to the protocols we support
* @param {PeerInfo} options.peerInfo The peer running the identify service
* @param {Libp2p} options.libp2p
* @param {Map<string, handler>} options.protocols - A reference to the protocols we support
*/
constructor (options) {
constructor ({ libp2p, protocols }) {
/**
* @property {Registrar}
* @property {PeerStore}
*/
this.registrar = options.registrar
/**
* @property {PeerInfo}
*/
this.peerInfo = options.peerInfo
this.peerStore = libp2p.peerStore
this._protocols = options.protocols
/**
* @property {ConnectionManager}
*/
this.connectionManager = libp2p.connectionManager
this.connectionManager.on('peer:connect', (connection) => {
const peerId = connection.remotePeer
this.identify(connection, peerId).catch(log.error)
})
/**
* @property {PeerId}
*/
this.peerId = libp2p.peerId
/**
* @property {AddressManager}
*/
this._libp2p = libp2p
this._protocols = protocols
this.handleMessage = this.handleMessage.bind(this)
// Store self host metadata
this._host = {
agentVersion: AGENT_VERSION,
protocolVersion: PROTOCOL_VERSION,
...libp2p._options.host
}
this.peerStore.metadataBook.set(this.peerId, 'AgentVersion', uint8ArrayFromString(this._host.agentVersion))
this.peerStore.metadataBook.set(this.peerId, 'ProtocolVersion', uint8ArrayFromString(this._host.protocolVersion))
}
/**
* 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.peerInfo.multiaddrs.toArray().map((ma) => ma.buffer),
protocols: Array.from(this._protocols.keys())
listenAddrs,
signedPeerRecord,
protocols
}],
pb.encode(Message),
stream,
@ -129,13 +131,14 @@ class IdentifyService {
/**
* Calls `push` for all peers in the `peerStore` that are connected
*
* @param {PeerStore} peerStore
*/
pushToPeerStore (peerStore) {
const connections = []
let connection
for (const peer of peerStore.peers.values()) {
if (peer.protocols.has(MULTICODEC_IDENTIFY_PUSH) && (connection = this.registrar.getConnection(peer))) {
if (peer.protocols.includes(MULTICODEC_IDENTIFY_PUSH) && (connection = this.connectionManager.get(peer.id))) {
connections.push(connection)
}
}
@ -178,11 +181,12 @@ class IdentifyService {
publicKey,
listenAddrs,
protocols,
observedAddr
observedAddr,
signedPeerRecord
} = message
const id = await PeerId.createFromPubKey(publicKey)
const peerInfo = new PeerInfo(id)
if (connection.remotePeer.toB58String() !== id.toB58String()) {
throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER)
}
@ -190,11 +194,26 @@ class IdentifyService {
// Get the observedAddr if there is one
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)
// Copy the listenAddrs and protocols
IdentifyService.updatePeerAddresses(peerInfo, listenAddrs)
IdentifyService.updatePeerProtocols(peerInfo, protocols)
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))
this.registrar.peerStore.replace(peerInfo)
// TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr)
}
@ -203,7 +222,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>}
@ -220,80 +239,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)
if (this.peerInfo.id.pubKey) {
publicKey = this.peerInfo.id.pubKey.bytes
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,
protocolVersion: this._host.protocolVersion,
agentVersion: this._host.agentVersion,
publicKey,
listenAddrs: this.peerInfo.multiaddrs.toArray().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 the listen addresses
const peerInfo = new PeerInfo(connection.remotePeer)
const id = connection.remotePeer
try {
IdentifyService.updatePeerAddresses(peerInfo, message.listenAddrs)
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) {
return log.error('received invalid listen addrs', 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) {
log.error('received invalid addrs', err)
}
// Update the protocols
IdentifyService.updatePeerProtocols(peerInfo, message.protocols)
this.peerStore.protoBook.set(id, message.protocols)
}
// Update the peer in the PeerStore
this.registrar.peerStore.replace(peerInfo)
/**
* 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,22 +6,26 @@ const globalThis = require('ipfs-utils/src/globalthis')
const log = debug('libp2p')
log.error = debug('libp2p:error')
const PeerInfo = require('peer-info')
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 { getPeerInfo } = require('./get-peer-info')
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')
const Circuit = require('./circuit')
const Dialer = require('./dialer')
const Keychain = require('./keychain')
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')
const {
@ -31,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 {
@ -42,76 +44,98 @@ class Libp2p extends EventEmitter {
// and add default values where appropriate
this._options = validateConfig(_options)
this.peerId = this._options.peerId
this.datastore = this._options.datastore
this.peerInfo = this._options.peerInfo
this.peerStore = new PeerStore()
this.peerStore = (this.datastore && this._options.peerStore.persistence)
? new PersistentPeerStore({
peerId: this.peerId,
datastore: this.datastore,
...this._options.peerStore
})
: new PeerStore({ peerId: this.peerId })
// Addresses {listen, announce, noAnnounce}
this.addresses = this._options.addresses
this.addressManager = new AddressManager(this._options.addresses)
this._modules = this._options.modules
this._config = this._options.config
this._transport = [] // Transport instances/references
this._discovery = new Map() // Discovery service instances/references
// Create the Connection Manager
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) {
this.metrics = new Metrics(this._options.metrics)
this.metrics = new Metrics({
...this._options.metrics,
connectionManager: this.connectionManager
})
}
// Create keychain
if (this._options.keychain && this._options.keychain.datastore) {
log('creating keychain')
const keychainOpts = Keychain.generateOptions()
this.keychain = new Keychain(this._options.keychain.datastore, {
passPhrase: this._options.keychain.pass,
...keychainOpts,
...this._options.keychain
})
log('keychain constructed')
}
// Setup the Upgrader
this.upgrader = new Upgrader({
localPeer: this.peerInfo.id,
localPeer: this.peerId,
metrics: this.metrics,
onConnection: (connection) => {
const peerInfo = this.peerStore.put(new PeerInfo(connection.remotePeer), { silent: true })
this.registrar.onConnect(peerInfo, connection)
this.connectionManager.onConnect(connection)
this.emit('peer:connect', peerInfo)
// Run identify for every connection
if (this.identifyService) {
this.identifyService.identify(connection, connection.remotePeer)
.catch(log.error)
}
},
onConnectionEnd: (connection) => {
const peerInfo = Dialer.getDialable(connection.remotePeer)
this.registrar.onDisconnect(peerInfo, connection)
this.connectionManager.onDisconnect(connection)
// If there are no connections to the peer, disconnect
if (!this.registrar.getConnection(peerInfo)) {
this.emit('peer:disconnect', peerInfo)
this.metrics && this.metrics.onPeerDisconnected(peerInfo.id)
}
}
onConnection: (connection) => this.connectionManager.onConnect(connection),
onConnectionEnd: (connection) => this.connectionManager.onDisconnect(connection)
})
// Create the Registrar
this.registrar = new Registrar({ peerStore: this.peerStore })
this.handle = this.handle.bind(this)
this.registrar.handle = this.handle
// Create the Connection Manager
this.connectionManager = new ConnectionManager(this, this._options.connectionManager)
// Setup the transport manager
this.transportManager = new TransportManager({
libp2p: this,
upgrader: this.upgrader
upgrader: this.upgrader,
faultTolerance: this._options.transportManager.faultTolerance
})
// Create the Registrar
this.registrar = new Registrar({
peerStore: this.peerStore,
connectionManager: this.connectionManager
})
this.handle = this.handle.bind(this)
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,
peerStore: this.peerStore,
concurrency: this._options.dialer.maxParallelDials,
perPeerLimit: this._options.dialer.maxDialsPerPeer,
timeout: this._options.dialer.dialTimeout
timeout: this._options.dialer.dialTimeout,
resolvers: this._options.dialer.resolvers
})
this._modules.transport.forEach((Transport) => {
@ -133,8 +157,7 @@ class Libp2p extends EventEmitter {
// Add the identify service since we can multiplex
this.identifyService = new IdentifyService({
registrar: this.registrar,
peerInfo: this.peerInfo,
libp2p: this,
protocols: this.upgrader.protocols
})
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
@ -151,8 +174,9 @@ class Libp2p extends EventEmitter {
if (this._modules.dht) {
const DHT = this._modules.dht
this._dht = new DHT({
libp2p: this,
dialer: this.dialer,
peerInfo: this.peerInfo,
peerId: this.peerId,
peerStore: this.peerStore,
registrar: this.registrar,
datastore: this.datastore,
@ -160,9 +184,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
@ -179,6 +205,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}
@ -198,6 +225,7 @@ class Libp2p extends EventEmitter {
*/
async start () {
log('libp2p is starting')
try {
await this._onStarting()
await this._onDidStart()
@ -212,8 +240,9 @@ class Libp2p extends EventEmitter {
/**
* Stop the libp2p node by closing its listeners and open connections
*
* @async
* @returns {void}
* @returns {Promise<void>}
*/
async stop () {
log('libp2p is stopping')
@ -227,7 +256,8 @@ class Libp2p extends EventEmitter {
this._discovery = new Map()
this.connectionManager.stop()
await this.peerStore.stop()
await this.connectionManager.stop()
await Promise.all([
this.pubsub && this.pubsub.stop(),
@ -236,7 +266,6 @@ class Libp2p extends EventEmitter {
])
await this.transportManager.close()
await this.registrar.close()
ping.unmount(this)
this.dialer.destroy()
@ -250,6 +279,21 @@ class Libp2p extends EventEmitter {
log('libp2p has stopped')
}
/**
* Load keychain keys from the datastore.
* Imports the private key as 'self', if needed.
*
* @async
* @returns {void}
*/
async loadKeychain () {
try {
await this.keychain.findKeyByName('self')
} catch (err) {
await this.keychain.importPeer('self', this.peerId)
}
}
isStarted () {
return this._isStarted
}
@ -257,17 +301,18 @@ 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 () {
return this.registrar.connections
return this.connectionManager.connections
}
/**
* Dials to the provided peer. If successful, the `PeerInfo` of the
* Dials to the provided peer. If successful, the known metadata of the
* peer will be added to the nodes `peerStore`
*
* @param {PeerInfo|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>}
@ -278,26 +323,29 @@ class Libp2p extends EventEmitter {
/**
* Dials to the provided peer and handshakes with the given protocol.
* If successful, the `PeerInfo` of the peer will be added to the nodes `peerStore`,
* and the `Connection` will be sent in the callback
* If successful, the known metadata of the peer will be added to the nodes `peerStore`,
* and the `Connection` will be returned
*
* @async
* @param {PeerInfo|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 dialable = Dialer.getDialable(peer)
let connection
if (PeerInfo.isPeerInfo(dialable)) {
this.peerStore.put(dialable, { silent: true })
connection = this.registrar.getConnection(dialable)
const { id, multiaddrs } = getPeer(peer)
if (id.equals(this.peerId)) {
throw errCode(new Error('Cannot dial self'), codes.ERR_DIALED_SELF)
}
let connection = this.connectionManager.get(id)
if (!connection) {
connection = await this.dialer.connectToPeer(dialable, options)
connection = await this.dialer.connectToPeer(peer, options)
} else if (multiaddrs) {
this.peerStore.addressBook.add(id, multiaddrs)
}
// If a protocol was provided, create a new stream
@ -308,16 +356,45 @@ class Libp2p extends EventEmitter {
return connection
}
/**
* 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.
*
* @returns {Array<Multiaddr>}
*/
get multiaddrs () {
// Filter noAnnounce multiaddrs
const filterMa = this.addressManager.getNoAnnounceAddrs()
// Create advertising list
return this.transportManager.getAddrs()
.concat(this.addressManager.getAnnounceAddrs())
.filter((ma, index, array) => {
// Filter out if repeated
if (array.findIndex((otherMa) => otherMa.equals(ma)) !== index) {
return false
}
// Filter out if in noAnnounceMultiaddrs
if (filterMa.find((fm) => fm.equals(ma))) {
return false
}
return true
})
}
/**
* Disconnects all connections to the given `peer`
*
* @param {PeerInfo|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) {
const peerInfo = getPeerInfo(peer, this.peerStore)
const { id } = getPeer(peer)
const connections = this.registrar.connections.get(peerInfo.id.toB58String())
const connections = this.connectionManager.connections.get(id.toB58String())
if (!connections) {
return
@ -331,18 +408,25 @@ class Libp2p extends EventEmitter {
}
/**
* Pings the given peer
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to ping
* Pings the given peer in order to obtain the operation latency.
*
* @param {PeerId|Multiaddr|string} peer - The peer to ping
* @returns {Promise<number>}
*/
async ping (peer) {
const peerInfo = await getPeerInfo(peer, this.peerStore)
ping (peer) {
const { id, multiaddrs } = getPeer(peer)
return ping(this, peerInfo)
// 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
*/
@ -361,6 +445,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) {
@ -376,17 +461,11 @@ class Libp2p extends EventEmitter {
}
async _onStarting () {
// Listen on the addresses supplied in the peerInfo
const multiaddrs = this.peerInfo.multiaddrs.toArray()
// Listen on the provided transports
await this.transportManager.listen()
await this.transportManager.listen(multiaddrs)
// The addresses may change once the listener starts
// eg /ip4/0.0.0.0/tcp/0 => /ip4/192.168.1.0/tcp/58751
this.peerInfo.multiaddrs.clear()
for (const ma of this.transportManager.getAddrs()) {
this.peerInfo.multiaddrs.add(ma)
}
// Start PeerStore
await this.peerStore.start()
if (this._config.pubsub.enabled) {
this.pubsub && this.pubsub.start()
@ -407,57 +486,62 @@ 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', peerInfo => {
this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerInfo)
this.peerStore.on('peer', peerId => {
this.emit('peer:discovery', peerId)
this._maybeConnect(peerId)
})
// 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.connectionManager.start()
// Peer discovery
await this._setupPeerDiscovery()
// Once we start, emit and dial any peers we may have already discovered
for (const peerInfo of this.peerStore.peers.values()) {
this.emit('peer:discovery', peerInfo)
this._maybeConnect(peerInfo)
}
}
/**
* Called whenever peer discovery services emit `peer` events.
* Known peers may be emitted.
*
* @private
* @param {PeerInfo} peerInfo
* @param {{ id: PeerId, multiaddrs: Array<Multiaddr>, protocols: Array<string> }} peer
*/
_onDiscoveryPeer (peerInfo) {
if (peerInfo.id.toB58String() === this.peerInfo.id.toB58String()) {
_onDiscoveryPeer (peer) {
if (peer.id.toB58String() === this.peerId.toB58String()) {
log.error(new Error(codes.ERR_DISCOVERED_SELF))
return
}
this.peerStore.put(peerInfo)
peer.multiaddrs && this.peerStore.addressBook.add(peer.id, peer.multiaddrs)
peer.protocols && this.peerStore.protoBook.set(peer.id, peer.protocols)
}
/**
* Will dial to the given `peerInfo` if the current number of
* Will dial to the given `peerId` if the current number of
* connected peers is less than the configured `ConnectionManager`
* minPeers.
* minConnections.
*
* @private
* @param {PeerInfo} peerInfo
* @param {PeerId} peerId
*/
async _maybeConnect (peerInfo) {
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.registrar.getConnection(peerInfo)) {
const minPeers = this._options.connectionManager.minPeers || 0
if (minPeers > this.connectionManager._connections.size) {
log('connecting to discovered peer %s', peerInfo.id.toB58String())
if (this._config.peerDiscovery.autoDial === true && !this.connectionManager.get(peerId)) {
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(peerInfo)
await this.dialer.connectToPeer(peerId)
} catch (err) {
log.error('could not connect to discovered peer', err)
}
@ -488,7 +572,10 @@ class Libp2p extends EventEmitter {
let discoveryService
if (typeof DiscoveryService === 'function') {
discoveryService = new DiscoveryService(Object.assign({}, config, { peerInfo: this.peerInfo, libp2p: this }))
discoveryService = new DiscoveryService(Object.assign({}, config, {
peerId: this.peerId,
libp2p: this
}))
} else {
discoveryService = DiscoveryService
}
@ -515,19 +602,20 @@ class Libp2p extends EventEmitter {
}
/**
* Like `new Libp2p(options)` except it will create a `PeerInfo`
* Like `new Libp2p(options)` except it will create a `PeerId`
* instance if one is not provided in options.
* @param {object} options Libp2p configuration options
* @returns {Libp2p}
*
* @param {object} options - Libp2p configuration options
* @returns {Promise<Libp2p>}
*/
Libp2p.create = async function create (options = {}) {
if (options.peerInfo) {
if (options.peerId) {
return new Libp2p(options)
}
const peerInfo = await PeerInfo.create()
const peerId = await PeerId.create()
options.peerInfo = peerInfo
options.peerId = peerId
return new Libp2p(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()
}

55
src/keychain/README.md Normal file
View File

@ -0,0 +1,55 @@
# js-libp2p-keychain
> A secure key chain for libp2p in JavaScript
## Features
- Manages the lifecycle of a key
- Keys are encrypted at rest
- Enforces the use of safe key names
- Uses encrypted PKCS 8 for key storage
- Uses PBKDF2 for a "stetched" key encryption key
- Enforces NIST SP 800-131A and NIST SP 800-132
- Uses PKCS 7: CMS (aka RFC 5652) to provide cryptographically protected messages
- Delays reporting errors to slow down brute force attacks
### KeyInfo
The key management and naming service API all return a `KeyInfo` object. The `id` is a universally unique identifier for the key. The `name` is local to the key chain.
```js
{
name: 'rsa-key',
id: 'QmYWYSUZ4PV6MRFYpdtEDJBiGs4UrmE6g8wmAWSePekXVW'
}
```
The **key id** is the SHA-256 [multihash](https://github.com/multiformats/multihash) of its public key. The *public key* is a [protobuf encoding](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/keys.proto.js) containing a type and the [DER encoding](https://en.wikipedia.org/wiki/X.690) of the PKCS [SubjectPublicKeyInfo](https://www.ietf.org/rfc/rfc3279.txt).
### Private key storage
A private key is stored as an encrypted PKCS 8 structure in the PEM format. It is protected by a key generated from the key chain's *passPhrase* using **PBKDF2**.
The default options for generating the derived encryption key are in the `dek` object. This, along with the passPhrase, is the input to a `PBKDF2` function.
```js
const defaultOptions = {
//See https://cryptosense.com/parameter-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 1000,
salt: 'at least 16 characters long',
hash: 'sha2-512'
}
}
```
![key storage](./doc/private-key.png?raw=true)
### Physical storage
The actual physical storage of an encrypted key is left to implementations of [interface-datastore](https://github.com/ipfs/interface-datastore/). A key benifit is that now the key chain can be used in browser with the [js-datastore-level](https://github.com/ipfs/js-datastore-level) implementation.
### Cryptographic Message Syntax (CMS)
CMS, aka [PKCS #7](https://en.wikipedia.org/wiki/PKCS) and [RFC 5652](https://tools.ietf.org/html/rfc5652), describes an encapsulation syntax for data protection. It is used to digitally sign, digest, authenticate, or encrypt arbitrary message content. Basically, `cms.encrypt` creates a DER message that can be only be read by someone holding the private key.

124
src/keychain/cms.js Normal file
View File

@ -0,0 +1,124 @@
'use strict'
require('node-forge/lib/pkcs7')
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)
*
* CMS describes an encapsulation syntax for data protection. It
* is used to digitally sign, digest, authenticate, or encrypt
* arbitrary message content.
*
* See RFC 5652 for all the details.
*/
class CMS {
/**
* Creates a new instance with a keychain
*
* @param {Keychain} keychain - the available keys
*/
constructor (keychain) {
if (!keychain) {
throw errcode(new Error('keychain is required'), 'ERR_KEYCHAIN_REQUIRED')
}
this.keychain = keychain
}
/**
* Creates some protected data.
*
* The output Uint8Array contains the PKCS #7 message in DER.
*
* @param {string} name - The local key name.
* @param {Uint8Array} plain - The data to encrypt.
* @returns {undefined}
*/
async encrypt (name, plain) {
if (!(plain instanceof Uint8Array)) {
throw errcode(new Error('Plain data must be a Uint8Array'), 'ERR_INVALID_PARAMS')
}
const key = await this.keychain.findKeyByName(name)
const pem = await this.keychain._getPrivateKey(name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
const certificate = await certificateForKey(key, privateKey)
// create a p7 enveloped message
const p7 = forge.pkcs7.createEnvelopedData()
p7.addRecipient(certificate)
p7.content = forge.util.createBuffer(plain)
p7.encrypt()
// convert message to DER
const der = forge.asn1.toDer(p7.toAsn1()).getBytes()
return uint8ArrayFromString(der, 'ascii')
}
/**
* Reads some protected data.
*
* 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 {Uint8Array} cmsData - The CMS encrypted data to decrypt.
* @returns {undefined}
*/
async decrypt (cmsData) {
if (!(cmsData instanceof Uint8Array)) {
throw errcode(new Error('CMS data is required'), 'ERR_INVALID_PARAMS')
}
let cms
try {
const buf = forge.util.createBuffer(uint8ArrayToString(cmsData, 'ascii'))
const obj = forge.asn1.fromDer(buf)
cms = forge.pkcs7.messageFromAsn1(obj)
} catch (err) {
throw errcode(new Error('Invalid CMS: ' + err.message), 'ERR_INVALID_CMS')
}
// Find a recipient whose key we hold. We only deal with recipient certs
// issued by ipfs (O=ipfs).
const recipients = cms.recipients
.filter(r => r.issuer.find(a => a.shortName === 'O' && a.value === 'ipfs'))
.filter(r => r.issuer.find(a => a.shortName === 'CN'))
.map(r => {
return {
recipient: r,
keyId: r.issuer.find(a => a.shortName === 'CN').value
}
})
const r = await findAsync(recipients, async (recipient) => {
try {
const key = await this.keychain.findKeyById(recipient.keyId)
if (key) return true
} catch (err) {
return false
}
return false
})
if (!r) {
const missingKeys = recipients.map(r => r.keyId)
throw errcode(new Error('Decryption needs one of the key(s): ' + missingKeys.join(', ')), 'ERR_MISSING_KEYS', {
missingKeys
})
}
const key = await this.keychain.findKeyById(r.keyId)
const pem = await this.keychain._getPrivateKey(key.name)
const privateKey = forge.pki.decryptRsaPrivateKey(pem, this.keychain._())
cms.decrypt(r.recipient, privateKey)
return uint8ArrayFromString(cms.content.getBytes(), 'ascii')
}
}
module.exports = CMS

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1 @@
<mxfile userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" version="7.8.2" editor="www.draw.io"><diagram id="a8b2919f-aefc-d24c-c550-ea0bf34e92af" name="Page-1">7VlNb6MwEP01HLfCGBJ6bNJ2V9pdqVIP2x4dcMAKYGScJumvXxNsvkw+SmgSVe2hMs9mbL839swQA07j9U+G0vAv9XFkWKa/NuC9YVmua4n/ObApAOjCAggY8QsIVMAzeccSNCW6JD7OGgM5pREnaRP0aJJgjzcwxBhdNYfNadScNUUB1oBnD0U6+o/4PJTbssYV/guTIFQzg9Ft0TND3iJgdJnI+QwLzrd/RXeMlC250SxEPl3VIPhgwCmjlBeteD3FUU6toq1473FHb7luhhN+zAtSpzcULeXWU5RluYmQoQzLRfKNIobjtbA7CXkcCQCIZsYZXeApjSgTSEITMXIyJ1HUglBEgkQ8emJlWOCTN8w4EZTfyY6Y+H4+zWQVEo6fU+Tlc66EfwlsSynOF22KJ7loYQCvd24clHQKL8U0xpxtxBDlolIA6aBgJJ9Xldy2hMKa0ko3JB0sKA1XJIuG5Lmbc6hx/jT5ff9oaWQL50jzZsqoh4Uq3dTUtBiAF9AmxtaJAVYHM6MBmLE1Zny8EABNOaFJ9nW9sfQryfr4fN7oaJxrNOPEv8sv1ZyvSFwPxGuSLjbJNi85GzcmGCvgdQvAUQk8YUbE8nK6a7xhX7uKD7JWo8XpoEVhDEeIk7em+S6u5AxPlIiJq6PQEgWMraaJjC6Zh+Vb9Uu2bUiFw12GOGIB5pqhrXTlto9SczSomk5Dyw9IJsL1dku1C+9SKpYHR5Fvmj1VhE1D2ukbTkX3WlQsuGmErbqw4KLnE5oHBDlWWbt10K22i+xQVgiANrVhaT4g271g22xfKI3kTDQKi33d5rY7fB4Mmgxn5B3NtgNy/5D7EKOdieHcfyhcRmiGo0mZBauwW+XBe+KlzOblSoxSz7pjunvj6A8RgcpaY9Mw3tfZ1BA6n2f41IOt6puaRAucrz/AiSbUNaR/Fjxj+geAxk668PJqRLiPexX8QPuS/OjVmo84yjhleqV2CXac9o18Vnb06uEm3e01PvWW8XZfh4iZFdn+n9mQTLWSCQhcjanRntB5ElF6yl9cQl++zGpfbo7unp9VZgE9M2dJoFFdbRmc5cRarRMLLd0P3S5KnAEoGWuUaHwcTHPXhL/U2q/NjPdF+k6tIHV6J8AqeF9PBtzyZxu2HLVvaQPdlqHhShswaG0zmLQdVWsRbb+lPV5avf44Qdpm2Vo/67JLnfb+oo86RDeNKxLdHkr0208TXcXGz/pW0S066C+61SG6/S36x0TXC7VTRP9SH43VLahyzHZpc/xHY7DfUG85xWP1A2MxvPoRFz78Bw==</diagram></mxfile>

469
src/keychain/index.js Normal file
View File

@ -0,0 +1,469 @@
/* eslint max-nested-callbacks: ["error", 5] */
'use strict'
const sanitize = require('sanitize-filename')
const mergeOptions = require('merge-options')
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/'
// NIST SP 800-132
const NIST = {
minKeyLength: 112 / 8,
minSaltLength: 128 / 8,
minIterationCount: 1000
}
const defaultOptions = {
// See https://cryptosense.com/parametesr-choice-for-pbkdf2/
dek: {
keyLength: 512 / 8,
iterationCount: 10000,
salt: 'you should override this value with a crypto secure random number',
hash: 'sha2-512'
}
}
function validateKeyName (name) {
if (!name) return false
if (typeof name !== 'string') return false
return name === sanitize(name.trim())
}
/**
* Throws an error after a delay
*
* This assumes than an error indicates that the keychain is under attack. Delay returning an
* error to make brute force attacks harder.
*
* @param {string | Error} err - The error
* @private
*/
async function throwDelayed (err) {
const min = 200
const max = 1000
const delay = Math.random() * (max - min) + min
await new Promise(resolve => setTimeout(resolve, delay))
throw err
}
/**
* Converts a key name into a datastore name.
*
* @param {string} name
* @returns {DS.Key}
* @private
*/
function DsName (name) {
return new DS.Key(keyPrefix + name)
}
/**
* Converts a key name into a datastore info name.
*
* @param {string} name
* @returns {DS.Key}
* @private
*/
function DsInfoName (name) {
return new DS.Key(infoPrefix + name)
}
/**
* Information about a key.
*
* @typedef {Object} KeyInfo
*
* @property {string} id - The universally unique key id.
* @property {string} name - The local key name.
*/
/**
* Manages the lifecycle of a key. Keys are encrypted at rest using PKCS #8.
*
* A key in the store has two entries
* - '/info/*key-name*', contains the KeyInfo for the key
* - '/pkcs8/*key-name*', contains the PKCS #8 for the key
*
*/
class Keychain {
/**
* Creates a new instance of a key chain.
*
* @param {DS} store - where the key are.
* @param {object} options - ???
*/
constructor (store, options) {
if (!store) {
throw new Error('store is required')
}
this.store = store
this.opts = mergeOptions(defaultOptions, options)
// Enforce NIST SP 800-132
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) {
throw new Error(`dek.keyLength must be least ${NIST.minKeyLength} bytes`)
}
if (this.opts.dek.salt.length < NIST.minSaltLength) {
throw new Error(`dek.saltLength must be least ${NIST.minSaltLength} bytes`)
}
if (this.opts.dek.iterationCount < NIST.minIterationCount) {
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
}
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) : ''
Object.defineProperty(this, '_', { value: () => dek })
}
/**
* Gets an object that can encrypt/decrypt protected data
* using the Cryptographic Message Syntax (CMS).
*
* CMS describes an encapsulation syntax for data protection. It
* is used to digitally sign, digest, authenticate, or encrypt
* arbitrary message content.
*
* @returns {CMS}
*/
get cms () {
return new CMS(this)
}
/**
* Generates the options for a keychain. A random salt is produced.
*
* @returns {object}
*/
static generateOptions () {
const options = Object.assign({}, defaultOptions)
const saltLength = Math.ceil(NIST.minSaltLength / 3) * 3 // no base64 padding
options.dek.salt = uint8ArrayToString(crypto.randomBytes(saltLength), 'base64')
return options
}
/**
* Gets an object that can encrypt/decrypt protected data.
* The default options for a keychain.
*
* @returns {object}
*/
static get options () {
return defaultOptions
}
/**
* Create a new key.
*
* @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. Used for rsa keys only.
* @returns {KeyInfo}
*/
async createKey (name, type, size) {
const self = this
if (!validateKeyName(name) || name === 'self') {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (typeof type !== 'string') {
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
}
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 (!Number.isSafeInteger(size) || size < 2048) {
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
}
break
default:
break
}
let keyInfo
try {
const keypair = await crypto.keys.generateKeyPair(type, size)
const kid = await keypair.id()
const pem = await keypair.export(this._())
keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, uint8ArrayFromString(pem))
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
await batch.commit()
} catch (err) {
return throwDelayed(err)
}
return keyInfo
}
/**
* List all the keys.
*
* @returns {KeyInfo[]}
*/
async listKeys () {
const self = this
const query = {
prefix: infoPrefix
}
const info = []
for await (const value of self.store.query(query)) {
info.push(JSON.parse(uint8ArrayToString(value.value)))
}
return info
}
/**
* Find a key by it's id.
*
* @param {string} id - The universally unique key identifier.
* @returns {KeyInfo}
*/
async findKeyById (id) {
try {
const keys = await this.listKeys()
return keys.find((k) => k.id === id)
} catch (err) {
return throwDelayed(err)
}
}
/**
* Find a key by it's name.
*
* @param {string} name - The local key name.
* @returns {KeyInfo}
*/
async findKeyByName (name) {
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
const dsname = DsInfoName(name)
try {
const res = await this.store.get(dsname)
return JSON.parse(uint8ArrayToString(res))
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
}
/**
* Remove an existing key.
*
* @param {string} name - The local key name; must already exist.
* @returns {KeyInfo}
*/
async removeKey (name) {
const self = this
if (!validateKeyName(name) || name === 'self') {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
const dsname = DsName(name)
const keyInfo = await self.findKeyByName(name)
const batch = self.store.batch()
batch.delete(dsname)
batch.delete(DsInfoName(name))
await batch.commit()
return keyInfo
}
/**
* Rename a key
*
* @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}
*/
async renameKey (oldName, newName) {
const self = this
if (!validateKeyName(oldName) || oldName === 'self') {
return throwDelayed(errcode(new Error(`Invalid old key name '${oldName}'`), 'ERR_OLD_KEY_NAME_INVALID'))
}
if (!validateKeyName(newName) || newName === 'self') {
return throwDelayed(errcode(new Error(`Invalid new key name '${newName}'`), 'ERR_NEW_KEY_NAME_INVALID'))
}
const oldDsname = DsName(oldName)
const newDsname = DsName(newName)
const oldInfoName = DsInfoName(oldName)
const newInfoName = DsInfoName(newName)
const exists = await self.store.has(newDsname)
if (exists) return throwDelayed(errcode(new Error(`Key '${newName}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
try {
const pem = await self.store.get(oldDsname)
const res = await self.store.get(oldInfoName)
const keyInfo = JSON.parse(uint8ArrayToString(res))
keyInfo.name = newName
const batch = self.store.batch()
batch.put(newDsname, pem)
batch.put(newInfoName, uint8ArrayFromString(JSON.stringify(keyInfo)))
batch.delete(oldDsname)
batch.delete(oldInfoName)
await batch.commit()
return keyInfo
} catch (err) {
return throwDelayed(err)
}
}
/**
* Export an existing key as a PEM encrypted PKCS #8 string
*
* @param {string} name - The local key name; must already exist.
* @param {string} password - The password
* @returns {string}
*/
async exportKey (name, password) {
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (!password) {
return throwDelayed(errcode(new Error('Password is required'), 'ERR_PASSWORD_REQUIRED'))
}
const dsname = DsName(name)
try {
const res = await this.store.get(dsname)
const pem = uint8ArrayToString(res)
const privateKey = await crypto.keys.import(pem, this._())
return privateKey.export(password)
} catch (err) {
return throwDelayed(err)
}
}
/**
* Import a new key from a PEM encoded PKCS #8 string
*
* @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}
*/
async importKey (name, pem, password) {
const self = this
if (!validateKeyName(name) || name === 'self') {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (!pem) {
return throwDelayed(errcode(new Error('PEM encoded key is required'), 'ERR_PEM_REQUIRED'))
}
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'))
let privateKey
try {
privateKey = await crypto.keys.import(pem, password)
} catch (err) {
return throwDelayed(errcode(new Error('Cannot read the key, most likely the password is wrong'), 'ERR_CANNOT_READ_KEY'))
}
let kid
try {
kid = await privateKey.id()
pem = await privateKey.export(this._())
} catch (err) {
return throwDelayed(err)
}
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, uint8ArrayFromString(pem))
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
await batch.commit()
return keyInfo
}
async importPeer (name, peer) {
const self = this
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
if (!peer || !peer.privKey) {
return throwDelayed(errcode(new Error('Peer.privKey is required'), 'ERR_MISSING_PRIVATE_KEY'))
}
const privateKey = peer.privKey
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'))
try {
const kid = await privateKey.id()
const pem = await privateKey.export(this._())
const keyInfo = {
name: name,
id: kid
}
const batch = self.store.batch()
batch.put(dsname, uint8ArrayFromString(pem))
batch.put(DsInfoName(name), uint8ArrayFromString(JSON.stringify(keyInfo)))
await batch.commit()
return keyInfo
} catch (err) {
return throwDelayed(err)
}
}
/**
* Gets the private key as PEM encoded PKCS #8 string.
*
* @param {string} name
* @returns {string}
* @private
*/
async _getPrivateKey (name) {
if (!validateKeyName(name)) {
return throwDelayed(errcode(new Error(`Invalid key name '${name}'`), 'ERR_INVALID_KEY_NAME'))
}
try {
const dsname = DsName(name)
const res = await this.store.get(dsname)
return uint8ArrayToString(res)
} catch (err) {
return throwDelayed(errcode(new Error(`Key '${name}' does not exist. ${err.message}`), 'ERR_KEY_NOT_FOUND'))
}
}
}
module.exports = Keychain

89
src/keychain/util.js Normal file
View File

@ -0,0 +1,89 @@
'use strict'
require('node-forge/lib/x509')
const forge = require('node-forge/lib/forge')
const pki = forge.pki
exports = module.exports
/**
* Gets a self-signed X.509 certificate for the key.
*
* 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 {Uint8Array}
*/
exports.certificateForKey = (key, privateKey) => {
const publicKey = pki.setRsaPublicKey(privateKey.n, privateKey.e)
const cert = pki.createCertificate()
cert.publicKey = publicKey
cert.serialNumber = '01'
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10)
const attrs = [{
name: 'organizationName',
value: 'ipfs'
}, {
shortName: 'OU',
value: 'keystore'
}, {
name: 'commonName',
value: key.id
}]
cert.setSubject(attrs)
cert.setIssuer(attrs)
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
}, {
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true
}])
// self-sign certificate
cert.sign(privateKey)
return cert
}
/**
* Finds the first item in a collection that is matched in the
* `asyncCompare` function.
*
* `asyncCompare` is an async function that must
* resolve to either `true` or `false`.
*
* @param {Array} array
* @param {function(*)} asyncCompare - An async function that returns a boolean
*/
async function findAsync (array, asyncCompare) {
const promises = array.map(asyncCompare)
const results = await Promise.all(promises)
const index = results.findIndex(result => result)
return array[index]
}
module.exports.findAsync = findAsync

View File

@ -21,6 +21,7 @@ class Metrics {
/**
*
* @param {object} options
* @param {ConnectionManager} options.connectionManager
* @param {number} options.computeThrottleMaxQueueSize
* @param {number} options.computeThrottleTimeout
* @param {Array<number>} options.movingAverageIntervals
@ -34,6 +35,10 @@ class Metrics {
this._oldPeers = oldPeerLRU(this._options.maxOldPeersRetention)
this._running = false
this._onMessage = this._onMessage.bind(this)
this._connectionManager = options.connectionManager
this._connectionManager.on('peer:disconnect', (connection) => {
this.onPeerDisconnected(connection.remotePeer)
})
}
/**
@ -61,6 +66,7 @@ class Metrics {
/**
* Gets the global `Stats` object
*
* @returns {Stats}
*/
get global () {
@ -69,6 +75,7 @@ class Metrics {
/**
* Returns a list of `PeerId` strings currently being tracked
*
* @returns {Array<string>}
*/
get peers () {
@ -78,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}
*/
@ -88,6 +96,7 @@ class Metrics {
/**
* Returns a list of all protocol strings currently being tracked.
*
* @returns {Array<string>}
*/
get protocols () {
@ -96,6 +105,7 @@ class Metrics {
/**
* Returns the `Stats` object for the given `protocol`.
*
* @param {string} protocol
* @returns {Stats}
*/
@ -107,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) {
@ -126,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 }) {
@ -162,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) {
@ -193,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 }) {
@ -210,7 +222,7 @@ class Metrics {
const _sink = stream.sink
stream.sink = source => {
pipe(
return pipe(
source,
tap(chunk => metrics._onMessage({
remotePeer,
@ -228,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,10 +15,10 @@ 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
* @returns {Promise<PeerInfo>}
* @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
if (!routers.length) {

View File

@ -1,3 +1,145 @@
# Peerstore
# PeerStore
WIP
Libp2p's PeerStore is responsible for keeping an updated register with the relevant information of the known peers. It should be the single source of truth for all peer data, where a subsystem can learn about peers' data and where someone can listen for updates. The PeerStore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`.
The PeerStore manages the high level operations on its inner books. Moreover, the PeerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter.
## Submitting records to the PeerStore
Several libp2p subsystems will perform operations that might gather relevant information about peers.
### Identify
- The Identify protocol automatically runs on every connection when multiplexing is enabled. The protocol will put the multiaddrs and protocols provided by the peer to the PeerStore.
- In the background, the Identify Service is also waiting for protocol change notifications of peers via the IdentifyPush protocol. Peers may leverage the `identify-push` message to communicate protocol changes to all connected peers, so that their PeerStore can be updated with the updated protocols.
- While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176).
- Taking into account that the Identify protocol records are directly from the peer, they should be considered the source of truth and weighted accordingly.
### Peer Discovery
- Libp2p discovery protocols aim to discover new peers in the network. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, a libp2p discovery protocol should emit a `peer` event with the information of the discovered peer and this information will be added to the PeerStore by libp2p.
### Dialer
- Libp2p API supports dialing a peer given a `multiaddr`, and no prior knowledge of the peer. If the node is able to establish a connection with the peer, it and its multiaddr is added to the PeerStore.
- When a connection is being upgraded, more precisely after its encryption, or even in a discovery protocol, a libp2p node can get to know other parties public keys. In this scenario, libp2p will add the peer's public key to its `KeyBook`.
### DHT
- On some DHT operations, such as finding providers for a given CID, nodes may exchange peer data as part of the query. This passive peer discovery should result in the DHT emitting the `peer` event in the same way [Peer Discovery](#peerdiscovery) does.
## Retrieving records from the PeerStore
When data in the PeerStore is updated the PeerStore will emit events based on the changes, to allow applications and other subsystems to take action on those changes. Any subsystem interested in these notifications should subscribe the [`PeerStore events`][peer-store-events].
### Peer
- Each time a new peer is discovered, the PeerStore should emit a [`peer` event][peer-store-events], so that interested parties can leverage this peer and establish a connection with it.
### Protocols
- When the known protocols of a peer change, the PeerStore emits a [`change:protocols` event][peer-store-events].
### Multiaddrs
- When the known listening `multiaddrs` of a peer change, the PeerStore emits a [`change:multiaddrs` event][peer-store-events].
## PeerStore implementation
The PeerStore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events.
### Components
#### Address Book
The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer may change over time and the Address Book must account for this.
`Map<string, Address>`
A `peerId.toB58String()` identifier mapping to a `Address` object, which should have the following structure:
```js
{
multiaddr: <Multiaddr>
}
```
#### Key Book
The `keyBook` tracks the public keys of the peers by keeping their [`PeerId`][peer-id].
`Map<string, PeerId`
A `peerId.toB58String()` identifier mapping to a `PeerId` of the peer. This instance contains the peer public key.
#### Protocol Book
The `protoBook` holds the identifiers of the protocols supported by each peer. The protocols supported by each peer are dynamic and will change over time.
`Map<string, Set<string>>`
A `peerId.toB58String()` identifier mapping to a `Set` of protocol identifier strings.
#### 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 (`Uint8Array`).
`Map<string, Map<string, Uint8Array>>`
A `peerId.toB58String()` identifier mapping to the peer metadata Map.
### API
For the complete API documentation, you should check the [API.md](../../doc/API.md).
Access to its underlying books:
- `peerStore.addressBook.*`
- `peerStore.keyBook.*`
- `peerStore.metadataBook.*`
- `peerStore.protoBook.*`
### Events
- `peer` - emitted when a new peer is added.
- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs.
- `change:protocols` - emitted when a known peer supports a different set of protocols.
- `change:pubkey` - emitted when a peer's public key is known.
- `change:metadata` - emitted when known metadata of a peer changes.
## Data Persistence
The data stored in the PeerStore can be persisted if configured appropriately. Keeping a record of the peers already discovered by the peer, as well as their known data aims to improve the efficiency of peers joining the network after being offline.
The libp2p node will need to receive a [datastore](https://github.com/ipfs/interface-datastore), in order to persist this data across restarts. A [datastore](https://github.com/ipfs/interface-datastore) stores its data in a key-value fashion. As a result, we need coherent keys so that we do not overwrite data.
The PeerStore should not continuously update the datastore whenever data is changed. Instead, it should only store new data after reaching a certain threshold of "dirty" peers, as well as when the node is stopped, in order to batch writes to the datastore.
The peer id will be appended to the datastore key for each data namespace. The namespaces were defined as follows:
**AddressBook**
All the known peer addresses are stored with a key pattern as follows:
`/peers/addrs/<b32 peer id no padding>`
**ProtoBook**
All the known peer protocols are stored with a key pattern as follows:
`/peers/protos/<b32 peer id no padding>`
**KeyBook**
All public keys are stored under the following pattern:
` /peers/keys/<b32 peer id no padding>`
**MetadataBook**
Metadata is stored under the following key pattern:
`/peers/metadata/<b32 peer id no padding>/<key>`
## Future Considerations
- If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating
- Further API methods will probably need to be added in the context of multiaddr validity and confidence.
- When improving libp2p configuration for specific runtimes, we should take into account the PeerStore recommended datastore.
- When improving libp2p configuration, we should think about a possible way of allowing the configuration of Bootstrap to be influenced by the persisted peers, as a way to decrease the load on Bootstrap nodes.
[peer-id]: https://github.com/libp2p/js-peer-id
[peer-store-events]: ../../doc/API.md#libp2ppeerstore

View File

@ -0,0 +1,346 @@
'use strict'
const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:address-book')
log.error = debug('libp2p:peer-store:address-book:error')
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
* of a peer.
*/
class AddressBook extends Book {
/**
* Address object
*
* @typedef {Object} Address
* @property {Multiaddr} multiaddr peer multiaddr.
* @property {boolean} isCertified obtained from a signed peer record.
*/
/**
* 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:
* "peer" - emitted when a peer is discovered by the node.
* "change:multiaddrs" - emitted when the known multiaddrs of a peer change.
*/
super({
peerStore,
eventName: 'change:multiaddrs',
eventProperty: 'multiaddrs',
eventTransformer: (data) => {
if (!data.addresses) {
return []
}
return data.addresses.map((address) => address.multiaddr)
}
})
/**
* 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
* @returns {AddressBook}
*/
set (peerId, multiaddrs) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const entry = this.data.get(id) || {}
const rec = entry.addresses
// Not replace multiaddrs
if (!addresses.length) {
return this
}
// Already knows the peer
if (rec && rec.length === addresses.length) {
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!
if (intersection.length === rec.length) {
log(`the addresses provided to store are equal to the already stored for ${id}`)
return this
}
}
this._setData(peerId, {
addresses,
record: entry.record
})
log(`stored provided multiaddrs for ${id}`)
// Notify the existance of a new peer
if (!rec) {
this._ps.emit('peer', peerId)
}
return this
}
/**
* 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}
*/
add (peerId, multiaddrs) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const entry = this.data.get(id) || {}
const rec = entry.addresses || []
// Add recorded uniquely to the new array (Union)
rec.forEach((addr) => {
if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) {
addresses.push(addr)
}
})
// If the recorded length is equal to the new after the unique union
// The content is the same, no need to update.
if (rec && rec.length === addresses.length) {
log(`the addresses provided to store are already stored for ${id}`)
return this
}
this._setData(peerId, {
addresses,
record: entry.record
})
log(`added provided multiaddrs for ${id}`)
// Notify the existance of a new peer
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, isCertified = false) {
if (!multiaddrs) {
log.error('multiaddrs must be provided to store data')
throw errcode(new Error('multiaddrs must be provided'), ERR_INVALID_PARAMETERS)
}
// create Address for each address
const addresses = []
multiaddrs.forEach((addr) => {
if (!multiaddr.isMultiaddr(addr)) {
log.error(`multiaddr ${addr} must be an instance of multiaddr`)
throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS)
}
addresses.push({
multiaddr: addr,
isCertified
})
})
return addresses
}
/**
* 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>|undefined}
*/
getMultiaddrsForPeer (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())
if (!entry || !entry.addresses) {
return undefined
}
return entry.addresses.map((address) => {
const multiaddr = address.multiaddr
const idString = multiaddr.getPeerId()
if (idString && idString === peerId.toB58String()) return multiaddr
return multiaddr.encapsulate(`/p2p/${peerId.toB58String()}`)
})
}
}
module.exports = AddressBook

120
src/peer-store/book.js Normal file
View File

@ -0,0 +1,120 @@
'use strict'
const errcode = require('err-code')
const PeerId = require('peer-id')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
const passthrough = data => data
/**
* The Book is the skeleton for the PeerStore books.
*/
class Book {
/**
* @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.
*/
constructor ({ peerStore, eventName, eventProperty, eventTransformer = passthrough }) {
this._ps = peerStore
this.eventName = eventName
this.eventProperty = eventProperty
this.eventTransformer = eventTransformer
/**
* Map known peers to their data.
*
* @type {Map<string, Array<Data>}
*/
this.data = new Map()
}
/**
* Set known data of a provided peer.
*
* @param {PeerId} peerId
* @param {Array<Data>|Data} data
*/
set (peerId, data) {
throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED')
}
/**
* 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.
* @returns {void}
*/
_setData (peerId, data, { emit = true } = {}) {
const b58key = peerId.toB58String()
// Store data in memory
this.data.set(b58key, data)
// Emit event
emit && this._emit(peerId, data)
}
/**
* Emit data.
*
* @private
* @param {PeerId} peerId
* @param {*} data
*/
_emit (peerId, data) {
this._ps.emit(this.eventName, {
peerId,
[this.eventProperty]: this.eventTransformer(data)
})
}
/**
* 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>|undefined}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
return rec ? [...rec] : undefined
}
/**
* Deletes the provided peer from the book.
*
* @param {PeerId} peerId
* @returns {boolean}
*/
delete (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!this.data.delete(peerId.toB58String())) {
return false
}
this._emit(peerId, [])
return true
}
}
module.exports = Book

View File

@ -6,247 +6,143 @@ const log = debug('libp2p:peer-store')
log.error = debug('libp2p:peer-store:error')
const { EventEmitter } = require('events')
const PeerId = require('peer-id')
const PeerInfo = require('peer-info')
const AddressBook = require('./address-book')
const KeyBook = require('./key-book')
const MetadataBook = require('./metadata-book')
const ProtoBook = require('./proto-book')
const {
ERR_INVALID_PARAMETERS
} = require('../errors')
/**
* Responsible for managing known peers, as well as their addresses and metadata
* @fires PeerStore#peer Emitted when a peer is connected to this node
* @fires PeerStore#change:protocols
* @fires PeerStore#change:multiaddrs
* 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.
* @fires PeerStore#change:pubkey Emitted emitted when a peer's public key is known.
* @fires PeerStore#change:metadata Emitted when the known metadata of a peer change.
*/
class PeerStore extends EventEmitter {
constructor () {
/**
* 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.
*/
/**
* @param {object} options
* @param {PeerId} options.peerId
* @class
*/
constructor ({ peerId }) {
super()
this._peerId = peerId
/**
* Map of peers
*
* @type {Map<string, PeerInfo>}
* AddressBook containing a map of peerIdStr to Address.
*/
this.peers = new Map()
this.addressBook = new AddressBook(this)
// TODO: Track ourselves. We should split `peerInfo` up into its pieces so we get better
// control and observability. This will be the initial step for removing PeerInfo
// https://github.com/libp2p/go-libp2p-core/blob/master/peerstore/peerstore.go
// this.addressBook = new Map()
// this.protoBook = new Map()
/**
* KeyBook containing a map of peerIdStr to their PeerId with public keys.
*/
this.keyBook = new KeyBook(this)
/**
* MetadataBook containing a map of peerIdStr to their metadata Map.
*/
this.metadataBook = new MetadataBook(this)
/**
* ProtoBook containing a map of peerIdStr to supported protocols.
*/
this.protoBook = new ProtoBook(this)
}
/**
* Stores the peerInfo of a new peer.
* If already exist, its info is updated. If `silent` is set to
* true, no 'peer' event will be emitted. This can be useful if you
* are already in the process of dialing the peer. The peer is technically
* known, but may not have been added to the PeerStore yet.
* @param {PeerInfo} peerInfo
* @param {object} [options]
* @param {boolean} [options.silent] (Default=false)
* @return {PeerInfo}
* Start the PeerStore.
*/
put (peerInfo, options = { silent: false }) {
if (!PeerInfo.isPeerInfo(peerInfo)) {
throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS)
}
let peer
// Already know the peer?
if (this.has(peerInfo.id)) {
peer = this.update(peerInfo)
} else {
peer = this.add(peerInfo)
// Emit the peer if silent = false
!options.silent && this.emit('peer', peerInfo)
}
return peer
}
start () {}
/**
* Add a new peer to the store.
* @param {PeerInfo} peerInfo
* @return {PeerInfo}
* Stop the PeerStore.
*/
add (peerInfo) {
if (!PeerInfo.isPeerInfo(peerInfo)) {
throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS)
}
stop () {}
// Create new instance and add values to it
const newPeerInfo = new PeerInfo(peerInfo.id)
/**
* Get all the stored information of every peer known.
*
* @returns {Map<string, Peer>}
*/
get peers () {
const storedPeers = new Set([
...this.addressBook.data.keys(),
...this.keyBook.data.keys(),
...this.protoBook.data.keys(),
...this.metadataBook.data.keys()
])
peerInfo.multiaddrs.forEach((ma) => newPeerInfo.multiaddrs.add(ma))
peerInfo.protocols.forEach((p) => newPeerInfo.protocols.add(p))
// Remove self peer if present
this._peerId && storedPeers.delete(this._peerId.toB58String())
const connectedMa = peerInfo.isConnected()
connectedMa && newPeerInfo.connect(connectedMa)
const peerProxy = new Proxy(newPeerInfo, {
set: (obj, prop, value) => {
if (prop === 'multiaddrs') {
this.emit('change:multiaddrs', {
peerInfo: obj,
multiaddrs: value.toArray()
})
} else if (prop === 'protocols') {
this.emit('change:protocols', {
peerInfo: obj,
protocols: Array.from(value)
})
}
return Reflect.set(...arguments)
}
const peersData = new Map()
storedPeers.forEach((idStr) => {
peersData.set(idStr, this.get(PeerId.createFromCID(idStr)))
})
this.peers.set(peerInfo.id.toB58String(), peerProxy)
return peerProxy
return peersData
}
/**
* Updates an already known peer.
* @param {PeerInfo} peerInfo
* @return {PeerInfo}
*/
update (peerInfo) {
if (!PeerInfo.isPeerInfo(peerInfo)) {
throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS)
}
const id = peerInfo.id.toB58String()
const recorded = this.peers.get(id)
// pass active connection state
const ma = peerInfo.isConnected()
if (ma) {
recorded.connect(ma)
}
// Verify new multiaddrs
// TODO: better track added and removed multiaddrs
const multiaddrsIntersection = [
...recorded.multiaddrs.toArray()
].filter((m) => peerInfo.multiaddrs.has(m))
if (multiaddrsIntersection.length !== peerInfo.multiaddrs.size ||
multiaddrsIntersection.length !== recorded.multiaddrs.size) {
for (const ma of peerInfo.multiaddrs.toArray()) {
recorded.multiaddrs.add(ma)
}
this.emit('change:multiaddrs', {
peerInfo: recorded,
multiaddrs: recorded.multiaddrs.toArray()
})
}
// Update protocols
// TODO: better track added and removed protocols
const protocolsIntersection = new Set(
[...recorded.protocols].filter((p) => peerInfo.protocols.has(p))
)
if (protocolsIntersection.size !== peerInfo.protocols.size ||
protocolsIntersection.size !== recorded.protocols.size) {
for (const protocol of peerInfo.protocols) {
recorded.protocols.add(protocol)
}
this.emit('change:protocols', {
peerInfo: recorded,
protocols: Array.from(recorded.protocols)
})
}
// Add the public key if missing
if (!recorded.id.pubKey && peerInfo.id.pubKey) {
recorded.id.pubKey = peerInfo.id.pubKey
}
return recorded
}
/**
* Get the info to the given id.
* @param {PeerId|string} peerId b58str id
* @returns {PeerInfo}
*/
get (peerId) {
// TODO: deprecate this and just accept `PeerId` instances
if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
}
return this.peers.get(peerId)
}
/**
* Has the info to the given id.
* @param {PeerId|string} peerId b58str id
* @returns {boolean}
*/
has (peerId) {
// TODO: deprecate this and just accept `PeerId` instances
if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
}
return this.peers.has(peerId)
}
/**
* Removes the Peer with the matching `peerId` from the PeerStore
* @param {PeerId|string} peerId b58str id
* Delete the information of the given peer in every book.
*
* @param {PeerId} peerId
* @returns {boolean} true if found and removed
*/
remove (peerId) {
// TODO: deprecate this and just accept `PeerId` instances
if (PeerId.isPeerId(peerId)) {
peerId = peerId.toB58String()
}
delete (peerId) {
const addressesDeleted = this.addressBook.delete(peerId)
const keyDeleted = this.keyBook.delete(peerId)
const protocolsDeleted = this.protoBook.delete(peerId)
const metadataDeleted = this.metadataBook.delete(peerId)
return this.peers.delete(peerId)
return addressesDeleted || keyDeleted || protocolsDeleted || metadataDeleted
}
/**
* Completely replaces the existing peers metadata with the given `peerInfo`
* @param {PeerInfo} peerInfo
* @returns {void}
* Get the stored information of a given peer.
*
* @param {PeerId} peerId
* @returns {Peer}
*/
replace (peerInfo) {
if (!PeerInfo.isPeerInfo(peerInfo)) {
throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS)
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
this.remove(peerInfo.id.toB58String())
this.add(peerInfo)
const id = this.keyBook.data.get(peerId.toB58String())
const addresses = this.addressBook.get(peerId)
const metadata = this.metadataBook.get(peerId)
const protocols = this.protoBook.get(peerId)
// This should be cleaned up in PeerStore v2
this.emit('change:multiaddrs', {
peerInfo,
multiaddrs: peerInfo.multiaddrs.toArray()
})
this.emit('change:protocols', {
peerInfo,
protocols: Array.from(peerInfo.protocols)
})
}
if (!id && !addresses && !metadata && !protocols) {
return undefined
}
/**
* Returns the known multiaddrs for a given `PeerInfo`. All returned multiaddrs
* will include the encapsulated `PeerId` of the peer.
* @param {PeerInfo} peer
* @returns {Array<Multiaddr>}
*/
multiaddrsForPeer (peer) {
return this.put(peer, true).multiaddrs.toArray().map(addr => {
const idString = addr.getPeerId()
if (idString && idString === peer.id.toB58String()) return addr
return addr.encapsulate(`/p2p/${peer.id.toB58String()}`)
})
return {
id: id || peerId,
addresses: addresses || [],
protocols: protocols || [],
metadata: metadata
}
}
}

View File

@ -0,0 +1,88 @@
'use strict'
const errcode = require('err-code')
const debug = require('debug')
const log = debug('libp2p:peer-store:key-book')
log.error = debug('libp2p:peer-store:key-book:error')
const PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The KeyBook is responsible for keeping the known public keys of a peer.
*/
class KeyBook extends Book {
/**
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
super({
peerStore,
eventName: 'change:pubkey',
eventProperty: 'pubkey',
eventTransformer: (data) => data.pubKey
})
/**
* Map known peers to their known Public Key.
*
* @type {Map<string, PeerId>}
*/
this.data = new Map()
}
/**
* Set the Peer public key.
*
* @override
* @param {PeerId} peerId
* @param {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey} publicKey
* @returns {KeyBook}
*/
set (peerId, publicKey) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const recPeerId = this.data.get(id)
// If no record available, and this is valid
if (!recPeerId && publicKey) {
// This might be unecessary, but we want to store the PeerId
// to avoid an async operation when reconstructing the PeerId
peerId.pubKey = publicKey
this._setData(peerId, peerId)
log(`stored provided public key for ${id}`)
}
return this
}
/**
* Get Public key of the given PeerId, if stored.
*
* @override
* @param {PeerId} peerId
* @returns {RsaPublicKey|Ed25519PublicKey|Secp256k1PublicKey}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
return rec ? rec.pubKey : undefined
}
}
module.exports = KeyBook

View File

@ -0,0 +1,168 @@
'use strict'
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 uint8ArrayEquals = require('uint8arrays/equals')
const PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The MetadataBook is responsible for keeping the known supported
* protocols of a peer.
*
* @fires MetadataBook#change:metadata
*/
class MetadataBook extends Book {
/**
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the MetadataBook to emit:
* "change:metadata" - emitted when the known metadata of a peer change.
*/
super({
peerStore,
eventName: 'change:metadata',
eventProperty: 'metadata'
})
/**
* Map known peers to their known protocols.
*
* @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 {Uint8Array} value - metadata value
* @returns {ProtoBook}
*/
set (peerId, key, value) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
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)
}
this._setValue(peerId, key, value)
return this
}
/**
* Set data into the datastructure
*
* @override
*/
_setValue (peerId, key, value, { emit = true } = {}) {
const id = peerId.toB58String()
const rec = this.data.get(id) || new Map()
const recMap = rec.get(key)
// Already exists and is equal
if (recMap && uint8ArrayEquals(value, recMap)) {
log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`)
return
}
rec.set(key, value)
this.data.set(id, rec)
emit && this._emit(peerId, key)
}
/**
* Get the known data of a provided peer.
*
* @param {PeerId} peerId
* @returns {Map<string, Uint8Array>}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
return this.data.get(peerId.toB58String())
}
/**
* Get specific metadata value, if it exists
*
* @param {PeerId} peerId
* @param {string} key
* @returns {Uint8Array}
*/
getValue (peerId, key) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
return rec && rec.get(key)
}
/**
* Deletes the provided peer from the book.
*
* @param {PeerId} peerId
* @returns {boolean}
*/
delete (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!this.data.delete(peerId.toB58String())) {
return false
}
this._emit(peerId)
return true
}
/**
* Deletes the provided peer metadata key from the book.
*
* @param {PeerId} peerId
* @param {string} key
* @returns {boolean}
*/
deleteValue (peerId, key) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
const rec = this.data.get(peerId.toB58String())
if (!rec || !rec.delete(key)) {
return false
}
this._emit(peerId, key)
return true
}
}
module.exports = MetadataBook

View File

@ -0,0 +1,15 @@
'use strict'
module.exports.NAMESPACE_COMMON = '/peers/'
// /peers/protos/<b32 peer id no padding>
module.exports.NAMESPACE_ADDRESS = '/peers/addrs/'
// /peers/keys/<b32 peer id no padding>
module.exports.NAMESPACE_KEYS = '/peers/keys/'
// /peers/metadata/<b32 peer id no padding>/<key>
module.exports.NAMESPACE_METADATA = '/peers/metadata/'
// /peers/addrs/<b32 peer id no padding>
module.exports.NAMESPACE_PROTOCOL = '/peers/protos/'

View File

@ -0,0 +1,389 @@
'use strict'
const debug = require('debug')
const log = debug('libp2p:persistent-peer-store')
log.error = debug('libp2p:persistent-peer-store:error')
const { Key } = require('interface-datastore')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const PeerStore = require('..')
const {
NAMESPACE_ADDRESS,
NAMESPACE_COMMON,
NAMESPACE_KEYS,
NAMESPACE_METADATA,
NAMESPACE_PROTOCOL
} = require('./consts')
const Addresses = require('./pb/address-book.proto')
const Protocols = require('./pb/proto-book.proto')
/**
* Responsible for managing the persistence of data in the PeerStore.
*/
class PersistentPeerStore extends PeerStore {
/**
* @class
* @param {Object} properties
* @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 ({ peerId, datastore, threshold = 5 }) {
super({ peerId })
/**
* Backend datastore used to persist data.
*/
this._datastore = datastore
/**
* Peers modified after the latest data persisted.
*/
this._dirtyPeers = new Set()
/**
* Peers metadata changed mapping peer identifers to metadata changed.
*
* @type {Map<string, Set<string>>}
*/
this._dirtyMetadata = new Map()
this.threshold = threshold
this._addDirtyPeer = this._addDirtyPeer.bind(this)
}
/**
* Start Persistent PeerStore.
*
* @returns {Promise<void>}
*/
async start () {
log('PeerStore is starting')
// Handlers for dirty peers
this.on('change:protocols', this._addDirtyPeer)
this.on('change:multiaddrs', this._addDirtyPeer)
this.on('change:pubkey', this._addDirtyPeerKey)
this.on('change:metadata', this._addDirtyPeerMetadata)
// Load data
for await (const entry of this._datastore.query({ prefix: NAMESPACE_COMMON })) {
await this._processDatastoreEntry(entry)
}
log('PeerStore started')
}
/**
* Stop Persistent PeerStore.
*
* @returns {Promise<void>}
*/
async stop () {
log('PeerStore is stopping')
this.removeAllListeners()
await this._commitData()
log('PeerStore stopped')
}
/**
* Add modified peer to the dirty set
*
* @private
* @param {Object} params
* @param {PeerId} params.peerId
*/
_addDirtyPeer ({ peerId }) {
const peerIdstr = peerId.toB58String()
log('add dirty peer', 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 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
* @param {string} params.metadata
*/
_addDirtyPeerMetadata ({ peerId, metadata }) {
const peerIdstr = peerId.toB58String()
log('add dirty metadata peer', peerIdstr)
this._dirtyPeers.add(peerIdstr)
// Add dirty metadata key
const mData = this._dirtyMetadata.get(peerIdstr) || new Set()
mData.add(metadata)
this._dirtyMetadata.set(peerIdstr, mData)
if (this._dirtyPeers.size >= this.threshold) {
// Commit current data
this._commitData().catch(err => {
log.error('error committing data', err)
})
}
}
/**
* Add all the peers current data to a datastore batch and commit it.
*
* @private
* @returns {Promise<void>}
*/
async _commitData () {
const commitPeers = Array.from(this._dirtyPeers)
if (!commitPeers.length) {
return
}
// Clear Dirty Peers set
this._dirtyPeers.clear()
log('create batch commit')
const batch = this._datastore.batch()
for (const peerIdStr of commitPeers) {
// PeerId
const peerId = this.keyBook.data.get(peerIdStr) || PeerId.createFromCID(peerIdStr)
// Address Book
this._batchAddressBook(peerId, batch)
// Key Book
!peerId.hasInlinePublicKey() && this._batchKeyBook(peerId, batch)
// Metadata Book
this._batchMetadataBook(peerId, batch)
// Proto Book
this._batchProtoBook(peerId, batch)
}
await batch.commit()
log('batch committed')
}
/**
* Add address book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchAddressBook (peerId, batch) {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_ADDRESS}${b32key}`)
const entry = this.addressBook.data.get(peerId.toB58String())
try {
// Deleted from the book
if (!entry) {
batch.delete(key)
return
}
const encodedData = Addresses.encode({
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)
} catch (err) {
log.error(err)
}
}
/**
* Add Key book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchKeyBook (peerId, batch) {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_KEYS}${b32key}`)
try {
// Deleted from the book
if (!peerId.pubKey) {
batch.delete(key)
return
}
const encodedData = peerId.marshalPubKey()
batch.put(key, encodedData)
} catch (err) {
log.error(err)
}
}
/**
* Add metadata book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchMetadataBook (peerId, batch) {
const b32key = peerId.toString()
const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || []
try {
dirtyMetada.forEach((dirtyKey) => {
const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`)
const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey)
if (dirtyValue) {
batch.put(key, dirtyValue)
} else {
batch.delete(key)
}
})
} catch (err) {
log.error(err)
}
}
/**
* Add proto book data of the peer to the batch.
*
* @private
* @param {PeerId} peerId
* @param {Object} batch
*/
_batchProtoBook (peerId, batch) {
const b32key = peerId.toString()
const key = new Key(`${NAMESPACE_PROTOCOL}${b32key}`)
const protocols = this.protoBook.get(peerId)
try {
// Deleted from the book
if (!protocols) {
batch.delete(key)
return
}
const encodedData = Protocols.encode({ protocols })
batch.put(key, encodedData)
} catch (err) {
log.error(err)
}
}
/**
* Process datastore entry and add its data to the correct book.
*
* @private
* @param {Object} params
* @param {Key} params.key - datastore key
* @param {Uint8Array} params.value - datastore value stored
* @returns {Promise<void>}
*/
async _processDatastoreEntry ({ key, value }) {
try {
const keyParts = key.toString().split('/')
const peerId = PeerId.createFromCID(keyParts[3])
let decoded
switch (keyParts[2]) {
case 'addrs':
decoded = Addresses.decode(value)
this.addressBook._setData(
peerId,
{
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':
decoded = await PeerId.createFromPubKey(value)
this.keyBook._setData(
decoded,
decoded,
{ emit: false })
break
case 'metadata':
this.metadataBook._setValue(
peerId,
keyParts[4],
value,
{ emit: false })
break
case 'protos':
decoded = Protocols.decode(value)
this.protoBook._setData(
peerId,
new Set(decoded.protocols),
{ emit: false })
break
default:
log('invalid data persisted for: ', key.toString())
}
} catch (err) {
log.error(err)
}
}
}
module.exports = PersistentPeerStore

View File

@ -0,0 +1,33 @@
'use strict'
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;
}
`
module.exports = protons(message).Addresses

View File

@ -0,0 +1,12 @@
'use strict'
const protons = require('protons')
/* eslint-disable no-tabs */
const message = `
message Protocols {
repeated string protocols = 1;
}
`
module.exports = protons(message).Protocols

View File

@ -0,0 +1,124 @@
'use strict'
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 PeerId = require('peer-id')
const Book = require('./book')
const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
/**
* The ProtoBook is responsible for keeping the known supported
* protocols of a peer.
*
* @fires ProtoBook#change:protocols
*/
class ProtoBook extends Book {
/**
* @class
* @param {PeerStore} peerStore
*/
constructor (peerStore) {
/**
* PeerStore Event emitter, used by the ProtoBook to emit:
* "change:protocols" - emitted when the known protocols of a peer change.
*/
super({
peerStore,
eventName: 'change:protocols',
eventProperty: 'protocols',
eventTransformer: (data) => Array.from(data)
})
/**
* Map known peers to their known protocols.
*
* @type {Map<string, Set<string>>}
*/
this.data = new Map()
}
/**
* 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
* @returns {ProtoBook}
*/
set (peerId, protocols) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!protocols) {
log.error('protocols must be provided to store data')
throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const recSet = this.data.get(id)
const newSet = new Set(protocols)
const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value))
// Already knows the peer and the recorded protocols are the same?
// If yes, no changes needed!
if (recSet && isSetEqual(recSet, newSet)) {
log(`the protocols provided to store are equal to the already stored for ${id}`)
return this
}
this._setData(peerId, newSet)
log(`stored provided protocols for ${id}`)
return this
}
/**
* 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}
*/
add (peerId, protocols) {
if (!PeerId.isPeerId(peerId)) {
log.error('peerId must be an instance of peer-id to store data')
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}
if (!protocols) {
log.error('protocols must be provided to store data')
throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS)
}
const id = peerId.toB58String()
const recSet = this.data.get(id) || new Set()
const newSet = new Set([...recSet, ...protocols]) // Set Union
// Any new protocol added?
if (recSet.size === newSet.size) {
log(`the protocols provided to store are already stored for ${id}`)
return this
}
protocols = [...newSet]
this._setData(peerId, newSet)
log(`added provided protocols for ${id}`)
return this
}
}
module.exports = 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 {PeerInfo} peer
* @returns {Promise<Number>}
* @param {PeerId|multiaddr} peer
* @returns {Promise<number>}
*/
async function ping (node, peer) {
log('dialing %s to %s', PROTOCOL, peer.id.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)
}

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