Skip to content

Commit

Permalink
docs: add migration guide for libp2p@0.46.x (#1883)
Browse files Browse the repository at this point in the history
Adds migration guide detailing breaking changes in libp2p@0.46.x

---------

Co-authored-by: Chad Nehemiah <chad.nehemiah94@gmail.com>
  • Loading branch information
achingbrain and maschad authored Jul 28, 2023
1 parent c999d6a commit 69c93ac
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 1 deletion.
234 changes: 234 additions & 0 deletions doc/migrations/v0.45-v0.46.md
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
```
2 changes: 1 addition & 1 deletion interop/BrowserDockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ ENV BROWSER=$BROWSER
ENV CI true

# manually specify runner until https://github.com/hugomrdias/playwright-test/issues/572 is resolved
ENTRYPOINT npm run test:interop:multidim -- --build false --types false -t browser -- --browser $BROWSER --runner mocha
ENTRYPOINT npm run test:interop:multidim -- --build false --types false -t browser -- --browser $BROWSER

0 comments on commit 69c93ac

Please sign in to comment.