-
Notifications
You must be signed in to change notification settings - Fork 446
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add migration guide for libp2p@0.46.x (#1883)
Adds migration guide detailing breaking changes in libp2p@0.46.x --------- Co-authored-by: Chad Nehemiah <chad.nehemiah94@gmail.com>
- Loading branch information
1 parent
c999d6a
commit 69c93ac
Showing
2 changed files
with
235 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
# Migrating to libp2p@46 <!-- omit in toc --> | ||
|
||
A migration guide for refactoring your application code from libp2p `v0.45` to `v0.46`. | ||
|
||
## Table of Contents <!-- omit in toc --> | ||
|
||
- [New features](#new-features) | ||
- [Manual identify](#manual-identify) | ||
- [Transient connections](#transient-connections) | ||
- [Breaking changes](#breaking-changes) | ||
- [Graceful stream closing](#graceful-stream-closing) | ||
- [Stream/Connection stat properties](#streamconnection-stat-properties) | ||
- [Interface module consolidation](#interface-module-consolidation) | ||
|
||
## New features | ||
|
||
### Manual identify | ||
|
||
The [identify protocol](https://github.com/libp2p/specs/blob/master/identify/README.md) is used by libp2p to discover which protocols a remote peer supports. By default it runs on every connection after it opens. | ||
|
||
Applications interested in peers that support certain protocols can register [topology callbacks](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.index.unknown.Topology.html) to be notified when network peers that support those protocols connect or disconnect. | ||
|
||
`libp2p@0.46.x` adds the ability for the user to fine-tune their identify usage and to run the identify protocol manually: | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import { identifyService } from 'libp2p/identify' | ||
|
||
const node = await createLibp2p({ | ||
services: { | ||
identify: identifyService({ | ||
// identify has stream limits so to prevent remote peers from closing | ||
// streams due to too many identify streams being opened in parallel, | ||
// so use this setting to disable running identify automatically. | ||
// | ||
// Note that this means you will need to run identify manually for | ||
// every connection that opens in order for topologies to work. | ||
// | ||
// Some modules such as KAD-DHT and Circuit Relay rely on this being | ||
// the case. | ||
runOnConnectionOpen: false | ||
}) | ||
} | ||
}) | ||
|
||
const conn = await node.dial('/ip4/123.123...') | ||
const identifyResult = await node.services.identify.identify(conn) | ||
``` | ||
|
||
Note that this is an advanced option and is not necessary for the vast majority of users. | ||
|
||
Most users will want to configure a topology instead to be notified when new peers are discovered that support a given protocol: | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import { identifyService } from 'libp2p/identify' | ||
|
||
const node = await createLibp2p({ | ||
services: { | ||
identify: identifyService() | ||
} | ||
}) | ||
|
||
node.register('/my/protocol', { | ||
onConnect (peer, connection) { | ||
// this is called after identify has completed and the peer has confirmed | ||
// which protocols it supports | ||
}, | ||
onDisconnect (peer, connection) { | ||
// handle disconnect | ||
} | ||
}) | ||
``` | ||
|
||
### Transient connections | ||
|
||
Some connections have limits applied to them by the remote. For example, as part of the [Circuit Relay v2 protocol](https://github.com/libp2p/specs/blob/master/relay/circuit-v2.md) relay servers are allowed to limit the amount of data transferred over a relayed connection and for how long. | ||
|
||
These connections are not expected to be long-lived and must be treated as slightly fragile as the remote may close them at any time. To detect this, these types of connections have a boolean `.transient` property set to `true`. | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import { identifyService } from 'libp2p/identify' | ||
|
||
const node = await createLibp2p({ /* ... */ }) | ||
|
||
// make a direct connection to a peer | ||
const conn1 = await node.dial('/ip4/123.123.123.123/tcp/123') | ||
console.info(conn1.transient) // false | ||
|
||
// make a connection to a peer via a relay server | ||
const conn2 = await node.dial('/ip4/.../p2p-circuit/...') | ||
console.info(conn2.transient) // true | ||
``` | ||
|
||
By default no protocols may run over a transient connection - to allow this protocols must explicitly opt-in to being run. This is in order to prevent high-bandwidth protocols from accidentally causing the remote to close the connection. | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import { identifyService } from 'libp2p/identify' | ||
|
||
const node = await createLibp2p({ | ||
// config here | ||
}) | ||
|
||
// register an incoming stream handler for a protocol that is allowed to run over | ||
// transient connections | ||
await node.register('/my/protocol', () => {}, { | ||
runOnTransientConnection: true | ||
}) | ||
|
||
// open a stream and allow the protocol to run over a transient connection | ||
const stream = await node.dialProtocol('/ip4/.../p2p-circuit/...', '/my/protocol', { | ||
runOnTransientConnection: true | ||
}) | ||
|
||
// the same flag can be passed to the `newStream` method on the connection itself | ||
const conn = await node.dial() | ||
conn.newStream('/my/protocol', { | ||
runOnTransientConnection: true | ||
}) | ||
``` | ||
|
||
## Breaking changes | ||
|
||
### Graceful stream closing | ||
|
||
Streams can either be closed gracefully, where we wait for any unsent data to be sent, or aborted in which case any unsent data is discarded and a reset message is sent, notifying the remote of the abnormal termination. | ||
|
||
To close a stream gracefully we call the `.close` method (or `.closeRead`/`.closeWrite` for when we want half-closed streams). To abort a stream we call `.abort` and pass an error object. | ||
|
||
In previous versions the `.close` method was synchronous which meant it could not wait for existing data to be sent which made nodes behave unpredictably. | ||
|
||
From `0.46.x` the `.close`/`.closeRead`/`.closeWrite` methods on the Stream interface are now asynchronous. `.abort` is a synchronous method that accepts an Error object. | ||
|
||
Similarly the Connection interface now has asynchronous `.close` and synchronous `.abort` methods. | ||
|
||
The `.reset` method has been removed from the Stream interface as it is only to be invoked internally by stream multiplexers when a remote stream reset has occurred. | ||
|
||
**Before** | ||
|
||
```js | ||
const stream = await libp2p.dialProtocol(multiaddr, '/my-protocol/1.0.0') | ||
|
||
// send some data | ||
await stream.sink([data]) | ||
|
||
// close the stream - previously this may not have waited for the data to be sent | ||
stream.close() | ||
|
||
// alternatively cause the stream to error on the remote | ||
stream.abort(new Error('Oh no!')) | ||
``` | ||
|
||
**After** | ||
|
||
```js | ||
const stream = await libp2p.dialProtocol(multiaddr, '/my-protocol/1.0.0') | ||
|
||
// send some data | ||
await stream.sink([data]) | ||
|
||
// close the stream - this method is now async | ||
await stream.close() | ||
|
||
// alternatively cause the stream to error on the remote | ||
stream.abort(new Error('Oh no!')) | ||
``` | ||
|
||
### Stream/Connection stat properties | ||
|
||
The properties on the `stream.stat` and `connection.stat` objects are now stored on the stream/connection itself. | ||
|
||
**Before** | ||
|
||
```js | ||
// stream.stat properties | ||
console.info(stream.stat.direction) | ||
console.info(stream.stat.timeline) | ||
console.info(stream.stat.protocol) | ||
|
||
// connection.stat properties | ||
console.info(connection.stat.direction) | ||
console.info(connection.stat.timeline) | ||
console.info(connection.stat.multiplexer) | ||
console.info(connection.stat.encryption) | ||
console.info(connection.stat.status) | ||
``` | ||
|
||
**After** | ||
|
||
```js | ||
// stream.stat properties | ||
console.info(stream.direction) | ||
console.info(stream.timeline) | ||
console.info(stream.protocol) | ||
|
||
// connection.stat properties | ||
console.info(connection.direction) | ||
console.info(connection.timeline) | ||
console.info(connection.multiplexer) | ||
console.info(connection.encryption) | ||
console.info(connection.status) | ||
``` | ||
|
||
### Interface module consolidation | ||
|
||
In an effort to prevent breaking changes affecting unrelated modules, libp2p prior to 0.46.x had a large number of single-issue interface modules for internal and external types - `@libp2p/address-manager`, `@libp2p/connection-gater`, `@libp2p/connection-manager` and so on. | ||
|
||
This meant that although we could release a new version of the address manager interface without impacting modules that only depended on the connection manager, releasing any change became a multiple-step process during which there was a time window sometimes lasting several days when the latest versions of modules would be incompatible with each other. | ||
|
||
Adding new methods and types to interfaces also became a breaking change since the existing released implementations of those interfaces would not implement the new methods which complicated matters further. | ||
|
||
Since [libp2p/js-libp2p#1792](https://github.com/libp2p/js-libp2p/pull/1792) converted libp2p into a monorepo project, a lot of these problems have gone away since we can now release multiple libp2p modules simultaneously. | ||
|
||
The urgency that required multiple interface modules has also subsided somewhat so now all libp2p interfaces are collected into two modules - `@lib2p2p/interface` for public-facing APIs and `@libp2p/interface-internal` for APIs designed to be consumed by libp2p components. | ||
|
||
**Before** | ||
|
||
```js | ||
import type { Libp2p } from '@libp2p/interface-libp2p' | ||
import type { AddressManager } from '@libp2p/interface-address-manager' | ||
import type { ConnectionManager } from '@libp2p/interface-connection-manager' | ||
// etc | ||
``` | ||
|
||
**After** | ||
|
||
```js | ||
import type { Libp2p } from '@libp2p/interface' | ||
import type { AddressManager } from '@libp2p/interface-internal/address-manager' | ||
import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager' | ||
// etc | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters