Skip to content

Commit 5123a83

Browse files
vasco-santosjacobheun
authored andcommitted
feat: peerStore persistence
1 parent 43630f1 commit 5123a83

13 files changed

+493
-63
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"err-code": "^2.0.0",
5151
"events": "^3.1.0",
5252
"hashlru": "^2.3.0",
53+
"interface-datastore": "^0.8.3",
5354
"ipfs-utils": "^2.2.0",
5455
"it-all": "^1.0.1",
5556
"it-buffer": "^0.1.2",

src/config.js

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const DefaultConfig = {
2020
metrics: {
2121
enabled: false
2222
},
23+
peerStore: {
24+
persistence: true
25+
},
2326
config: {
2427
dht: {
2528
enabled: false,

src/index.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const globalThis = require('ipfs-utils/src/globalthis')
66
const log = debug('libp2p')
77
log.error = debug('libp2p:error')
88

9+
const { MemoryDatastore } = require('interface-datastore')
910
const PeerId = require('peer-id')
1011

1112
const peerRouting = require('./peer-routing')
@@ -43,9 +44,12 @@ class Libp2p extends EventEmitter {
4344
// and add default values where appropriate
4445
this._options = validateConfig(_options)
4546

46-
this.datastore = this._options.datastore
4747
this.peerId = this._options.peerId
48-
this.peerStore = new PeerStore()
48+
this.datastore = this._options.datastore || new MemoryDatastore()
49+
this.peerStore = new PeerStore({
50+
datastore: this.datastore,
51+
...this._options.peerStore
52+
})
4953

5054
// Addresses {listen, announce, noAnnounce}
5155
this.addresses = this._options.addresses
@@ -393,6 +397,9 @@ class Libp2p extends EventEmitter {
393397
// Listen on the provided transports
394398
await this.transportManager.listen()
395399

400+
// Start PeerStore
401+
await this.peerStore.load()
402+
396403
if (this._config.pubsub.enabled) {
397404
this.pubsub && this.pubsub.start()
398405
}

src/peer-store/README.md

+44
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,51 @@ Access to its underlying books:
8383
- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs.
8484
- `change:protocols` - emitted when a known peer supports a different set of protocols.
8585

86+
## Data Persistence
87+
88+
The data stored in the PeerStore will be persisted by default. 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.
89+
90+
---
91+
TODO: Discuss if we should make it persisted by default now. Taking into consideration that we will use a MemoryDatastore by default, unless the user configures a datastore to use, it will be worthless. It might make sense to make it disabled by default until we work on improving configuration and provide good defauls for each environment.
92+
---
93+
94+
The libp2p node will need to receive a [datastore](https://github.com/ipfs/interface-datastore), in order to store this data in a persistent way. Otherwise, it will be stored on a [memory datastore](https://github.com/ipfs/interface-datastore/blob/master/src/memory.js).
95+
96+
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.
97+
98+
Taking into account that a datastore allows queries using a key prefix, we can find all the information if we define a consistent namespace that allow us to find the content without having any information. The namespaces were defined as follows:
99+
100+
**AddressBook**
101+
102+
All the knownw peer addresses are stored with a key pattern as follows:
103+
104+
`/peers/addrs/<b32 peer id no padding>`
105+
106+
**ProtoBook**
107+
108+
All the knownw peer protocols are stored with a key pattern as follows:
109+
110+
`/peers/protos/<b32 peer id no padding>`
111+
112+
**KeyBook**
113+
114+
_NOT_YET_IMPLEMENTED_
115+
116+
All public and private keys are stored under the following pattern:
117+
118+
` /peers/keys/<b32 peer id no padding>/{pub, priv}`
119+
120+
**MetadataBook**
121+
122+
_NOT_YET_IMPLEMENTED_
123+
124+
Metadata is stored under the following key pattern:
125+
126+
`/peers/metadata/<b32 peer id no padding>/<key>`
127+
86128
## Future Considerations
87129

88130
- If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating
89131
- Further API methods will probably need to be added in the context of multiaddr validity and confidence.
132+
- When improving libp2p configuration for specific runtimes, we should take into account the PeerStore recommended datastore.
133+
- 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.

src/peer-store/address-book.js

+21-17
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ const multiaddr = require('multiaddr')
99
const PeerId = require('peer-id')
1010

1111
const Book = require('./book')
12+
const Protobuf = require('./pb/address-book.proto')
1213

1314
const {
14-
ERR_INVALID_PARAMETERS
15+
codes: { ERR_INVALID_PARAMETERS }
1516
} = require('../errors')
1617

1718
/**
1819
* The AddressBook is responsible for keeping the known multiaddrs
1920
* of a peer.
21+
* This data will be persisted in the PeerStore datastore as follows:
22+
* /peers/addrs/<b32 peer id no padding>
2023
*/
2124
class AddressBook extends Book {
2225
/**
@@ -35,7 +38,20 @@ class AddressBook extends Book {
3538
* "peer" - emitted when a peer is discovered by the node.
3639
* "change:multiaddrs" - emitted when the known multiaddrs of a peer change.
3740
*/
38-
super(peerStore, 'change:multiaddrs', 'multiaddrs')
41+
super({
42+
peerStore,
43+
eventName: 'change:multiaddrs',
44+
eventProperty: 'multiaddrs',
45+
protoBuf: Protobuf,
46+
dsPrefix: '/peers/addrs/',
47+
eventTransformer: (data) => data.map((address) => address.multiaddr),
48+
dsSetTransformer: (data) => ({
49+
addrs: data.map((address) => address.multiaddr.buffer)
50+
}),
51+
dsGetTransformer: (data) => data.addrs.map((a) => ({
52+
multiaddr: multiaddr(a)
53+
}))
54+
})
3955

4056
/**
4157
* Map known peers to their known Addresses.
@@ -78,20 +94,14 @@ class AddressBook extends Book {
7894
}
7995
}
8096

81-
this.data.set(id, addresses)
82-
this._setPeerId(peerId)
97+
this._setData(peerId, addresses)
8398
log(`stored provided multiaddrs for ${id}`)
8499

85100
// Notify the existance of a new peer
86101
if (!rec) {
87102
this._ps.emit('peer', peerId)
88103
}
89104

90-
this._ps.emit('change:multiaddrs', {
91-
peerId,
92-
multiaddrs: addresses.map((mi) => mi.multiaddr)
93-
})
94-
95105
return this
96106
}
97107

@@ -127,16 +137,9 @@ class AddressBook extends Book {
127137
return this
128138
}
129139

130-
this._setPeerId(peerId)
131-
this.data.set(id, addresses)
132-
140+
this._setData(peerId, addresses)
133141
log(`added provided multiaddrs for ${id}`)
134142

135-
this._ps.emit('change:multiaddrs', {
136-
peerId,
137-
multiaddrs: addresses.map((mi) => mi.multiaddr)
138-
})
139-
140143
// Notify the existance of a new peer
141144
if (!rec) {
142145
this._ps.emit('peer', peerId)
@@ -147,6 +150,7 @@ class AddressBook extends Book {
147150

148151
/**
149152
* Transforms received multiaddrs into Address.
153+
* @private
150154
* @param {Array<Multiaddr>} multiaddrs
151155
* @returns {Array<Address>}
152156
*/

src/peer-store/book.js

+132-2
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,57 @@
11
'use strict'
22

33
const errcode = require('err-code')
4+
const debug = require('debug')
5+
const log = debug('libp2p:peer-store:book')
6+
log.error = debug('libp2p:peer-store:book:error')
7+
8+
const { Key } = require('interface-datastore')
49
const PeerId = require('peer-id')
510

611
const {
7-
ERR_INVALID_PARAMETERS
12+
codes: { ERR_INVALID_PARAMETERS }
813
} = require('../errors')
914

15+
const passthrough = data => data
16+
1017
/**
1118
* The Book is the skeleton for the PeerStore books.
19+
* It handles the PeerStore persistence and events.
1220
*/
1321
class Book {
14-
constructor (peerStore, eventName, eventProperty) {
22+
/**
23+
* @constructor
24+
* @param {Object} properties
25+
* @param {PeerStore} properties.peerStore PeerStore instance.
26+
* @param {string} properties.eventName Name of the event to emit by the PeerStore.
27+
* @param {string} properties.eventProperty Name of the property to emit by the PeerStore.
28+
* @param {Object} properties.protoBuf Suffix of the Datastore Key
29+
* @param {String} properties.dsPrefix Prefix of the Datastore Key
30+
* @param {String} [properties.dsSuffix] Suffix of the Datastore Key
31+
* @param {function} [properties.eventTransformer] Transformer function of the provided data for being emitted.
32+
* @param {function} [properties.dsSetTransformer] Transformer function of the provided data for being persisted.
33+
* @param {function} [properties.dsGetTransformer] Transformer function of the persisted data to be loaded.
34+
*/
35+
constructor ({
36+
peerStore,
37+
eventName,
38+
eventProperty,
39+
protoBuf,
40+
dsPrefix,
41+
dsSuffix = '',
42+
eventTransformer = passthrough,
43+
dsSetTransformer = passthrough,
44+
dsGetTransformer = passthrough
45+
}) {
1546
this._ps = peerStore
1647
this.eventName = eventName
1748
this.eventProperty = eventProperty
49+
this.protoBuf = protoBuf
50+
this.dsPrefix = dsPrefix
51+
this.dsSuffix = dsSuffix
52+
this.eventTransformer = eventTransformer
53+
this.dsSetTransformer = dsSetTransformer
54+
this.dsGetTransformer = dsGetTransformer
1855

1956
/**
2057
* Map known peers to their data.
@@ -23,6 +60,91 @@ class Book {
2360
this.data = new Map()
2461
}
2562

63+
/**
64+
* Load data from peerStore datastore into the books datastructures.
65+
* This will not persist the replicated data nor emit modify events.
66+
* @private
67+
* @return {Promise<void>}
68+
*/
69+
async _loadData () {
70+
if (!this._ps._datastore || !this._ps._enabledPersistance) {
71+
return
72+
}
73+
74+
const persistenceQuery = {
75+
prefix: this.dsPrefix
76+
}
77+
78+
for await (const { key, value } of this._ps._datastore.query(persistenceQuery)) {
79+
try {
80+
// PeerId to add to the book
81+
const b32key = key.toString()
82+
.replace(this.dsPrefix, '') // remove prefix from key
83+
.replace(this.dsSuffix, '') // remove suffix from key
84+
const peerId = PeerId.createFromCID(b32key)
85+
// Data in the format to add to the book
86+
const data = this.dsGetTransformer(this.protoBuf.decode(value))
87+
// Add the book without persist the replicated data and emit modify
88+
this._setData(peerId, data, {
89+
persist: false,
90+
emit: false
91+
})
92+
} catch (err) {
93+
log.error(err)
94+
}
95+
}
96+
}
97+
98+
/**
99+
* Set data into the datastructure, persistence and emit it using the provided transformers.
100+
* @private
101+
* @param {PeerId} peerId peerId of the data to store
102+
* @param {Array<*>} data data to store.
103+
* @param {Object} [options] storing options.
104+
* @param {boolean} [options.persist = true] persist the provided data.
105+
* @param {boolean} [options.emit = true] emit the provided data.
106+
* @return {Promise<void>}
107+
*/
108+
async _setData (peerId, data, { persist = true, emit = true } = {}) {
109+
const b58key = peerId.toB58String()
110+
111+
// Store data in memory
112+
this.data.set(b58key, data)
113+
this._setPeerId(peerId)
114+
115+
// Emit event
116+
emit && this._ps.emit(this.eventName, {
117+
peerId,
118+
[this.eventProperty]: this.eventTransformer(data)
119+
})
120+
121+
// Add to Persistence datastore
122+
persist && await this._persistData(peerId, data)
123+
}
124+
125+
/**
126+
* Persist data on the datastore
127+
* @private
128+
* @param {PeerId} peerId peerId of the data to persist
129+
* @param {Array<*>} data data to persist
130+
* @return {Promise<void>}
131+
*/
132+
async _persistData (peerId, data) {
133+
if (!this._ps._datastore || !this._ps._enabledPersistance) {
134+
return
135+
}
136+
137+
const b32key = peerId.toString()
138+
const k = `${this.dsPrefix}${b32key}${this.dsSuffix}`
139+
try {
140+
const value = this.protoBuf.encode(this.dsSetTransformer(data))
141+
142+
await this._ps._datastore.put(new Key(k), value)
143+
} catch (err) {
144+
log.error(err)
145+
}
146+
}
147+
26148
/**
27149
* Set known data of a provided peer.
28150
* @param {PeerId} peerId
@@ -75,9 +197,17 @@ class Book {
75197
[this.eventProperty]: []
76198
})
77199

200+
// Update Persistence datastore
201+
this._persistData(peerId, [])
202+
78203
return true
79204
}
80205

206+
/**
207+
* Set PeerId into peerStore datastructure.
208+
* @private
209+
* @param {PeerId} peerId
210+
*/
81211
_setPeerId (peerId) {
82212
if (!this._ps.peerIds.get(peerId)) {
83213
this._ps.peerIds.set(peerId.toB58String(), peerId)

0 commit comments

Comments
 (0)