A migration guide for refactoring your application code from libp2p v0.27.x to v0.28.0.
In libp2p@0.27
we integrated the PeerStore (former peer-book) into the codebase. By that time, it was not documented in the API DOC 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.
Before
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
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)
Before
const peerId = ...
const peerInfo = libp2p.peerStore.get(peerId)
// { id: PeerId, multiaddrs: MultiaddrSet, protocols: Set<string>}
After
const peerId = ...
const peer = libp2p.peerStore.get(peerId)
// { id: PeerId, addresses: Array<{ multiaddr: Multiaddr }>, protocols: Array<string> }
Before
const peerId = ...
const hasData = libp2p.peerStore.has(peerId)
After
const peerId = ...
const hasData = Boolean(libp2p.peerStore.get(peerId))
Before
libp2p.peerStore.remove(peerId)
After
// Atomic
libp2p.peerStore.protoBook.delete(peerId)
libp2p.peerStore.addressBook.delete(peerId)
// Remove the peer and ALL of its associated data
libp2p.peerStore.delete(peerId)
Before
const peers = libp2p.peerStore.peers
// Map<string, PeerInfo>
After
const peers = libp2p.peerStore.peers
// Similar to libp2p.peerStore.get()
// Map<string, { id: PeerId, addresses: Array<{ multiaddr: Multiaddr }>, protocols: Array<string> }
PeerInfo
is a libp2p peer abstraction layer that combines a PeerId
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
instances around. This also solves all the problems stated above, since subsystems will report new observations to the PeerStore.
While it was possible to create a libp2p node without providing a PeerInfo
, there were 2 use cases where a PeerInfo
was provided when creating a libp2p node.
libp2p.create
receives a peerId
property instead of a peerInfo
property.
Before
const peerId = ...
const peerInfo = new PeerInfo(peerId)
const libp2p = await Libp2p.create({
peerInfo
// ...
})
After
const peerId = ...
const libp2p = await Libp2p.create({
peerId
// ...
})
Before
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
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
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
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()
libp2p.dial
, libp2p.dialProtocol
, libp2p.hangup
and libp2p.ping
supported as the target parameter a PeerInfo
, a PeerId
, a Multiaddr
and a string representation of the multiaddr. Considering that PeerInfo
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
, 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
to PeerId
. When using a PeerInfo
, the PeerStore was not required to have the multiaddrs when they existed on the PeerInfo instance.
Before
const peerInfo = ... // PeerInfo containing its multiaddrs
const connection = await libp2p.dial(peerInfo)
After
const peerId = ...
// Known multiaddrs should be added to the PeerStore
libp2p.peerStore.addressBook.add(peerId, multiaddrs)
const connection = await libp2p.dial(peerId)
Both content-routing and peer-routing interfaces were modified to not return a 'PeerInfo' instance.
Before
for await (const peerInfo of libp2p.contentRouting.findProviders(cid)) {
// peerInfo is a PeerInfo instance
}
After
for await (const peer of libp2p.contentRouting.findProviders(cid)) {
// { id: PeerId, multiaddrs: Multiaddr[] }
}
Before
const peerInfo = await libp2p.peerRouting.findPeer(peerId)
// peerInfo is a PeerInfo instance
After
const peer = await libp2p.peerRouting.findPeer(peerId)
// { id: PeerId, multiaddrs: Multiaddr[] }
Registrar was introduced in libp2p@0.27
along with libp2p topologies. 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. 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
const connection = libp2p.registrar.getConnection(peerId)
After
const connection = libp2p.connectionManager.get(peerId)
Libp2p emits events whenever new connections are established. These emitted events previously providing the PeerInfo
of the peer that connected. In libp2p@0.28
these events are now emitted from the Connection Manager and will now emit the connection
itself.
Before
libp2p.on('peer:connect', (peerInfo) => {
// PeerInfo instance
})
libp2p.on('peer:disconnect', (peerInfo) => {
// PeerInfo instance
})
After
libp2p.connectionManager.on('peer:connect', (connection) => {
// Connection instance
})
libp2p.connectionManager.on('peer:disconnect', (connection) => {
// Connection instance
})
Before
libp2p.on('peer:discovery', (peerInfo) => {
// PeerInfo instance
})
After
libp2p.on('peer:discovery', (peerId) => {
// peerId instance
})