Skip to content

Latest commit

 

History

History
234 lines (169 loc) · 8.84 KB

v0.45-v0.46.md

File metadata and controls

234 lines (169 loc) · 8.84 KB

Migrating to libp2p@46

A migration guide for refactoring your application code from libp2p v0.45 to v0.46.

Table of Contents

New features

Manual identify

The identify protocol 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 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:

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:

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 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.

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.

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

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

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

// 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

// 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 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

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

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