Skip to content

Commit 84b935f

Browse files
vasco-santosjacobheun
authored andcommittedMay 28, 2020
feat: metadata book (#638)
* feat: metadata book * chore: address review * chore: address review
1 parent 0fbb597 commit 84b935f

11 files changed

+861
-39
lines changed
 

‎doc/API.md

+145
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
* [`peerStore.keyBook.delete`](#peerstorekeybookdelete)
3030
* [`peerStore.keyBook.get`](#peerstorekeybookget)
3131
* [`peerStore.keyBook.set`](#peerstorekeybookset)
32+
* [`peerStore.metadataBook.delete`](#peerstoremetadatabookdelete)
33+
* [`peerStore.metadataBook.deleteValue`](#peerstoremetadatabookdeletevalue)
34+
* [`peerStore.metadataBook.get`](#peerstoremetadatabookget)
35+
* [`peerStore.metadataBook.getValue`](#peerstoremetadatabookgetvalue)
36+
* [`peerStore.metadataBook.set`](#peerstoremetadatabookset)
3237
* [`peerStore.protoBook.add`](#peerstoreprotobookadd)
3338
* [`peerStore.protoBook.delete`](#peerstoreprotobookdelete)
3439
* [`peerStore.protoBook.get`](#peerstoreprotobookget)
@@ -939,6 +944,146 @@ const publicKey = peerId.pubKey
939944
peerStore.keyBook.set(peerId, publicKey)
940945
```
941946

947+
### peerStore.metadataBook.delete
948+
949+
Delete the provided peer from the book.
950+
951+
`peerStore.metadataBook.delete(peerId)`
952+
953+
#### Parameters
954+
955+
| Name | Type | Description |
956+
|------|------|-------------|
957+
| peerId | [`PeerId`][peer-id] | peerId to remove |
958+
959+
#### Returns
960+
961+
| Type | Description |
962+
|------|-------------|
963+
| `boolean` | true if found and removed |
964+
965+
#### Example
966+
967+
```js
968+
peerStore.metadataBook.delete(peerId)
969+
// false
970+
peerStore.metadataBook.set(peerId, 'nickname', Buffer.from('homePeer'))
971+
peerStore.metadataBook.delete(peerId)
972+
// true
973+
```
974+
975+
### peerStore.metadataBook.deleteValue
976+
977+
Deletes the provided peer metadata key-value pair from the book.
978+
979+
`peerStore.metadataBook.deleteValue(peerId, key)`
980+
981+
#### Parameters
982+
983+
| Name | Type | Description |
984+
|------|------|-------------|
985+
| peerId | [`PeerId`][peer-id] | peerId to remove |
986+
| key | `string` | key of the metadata value to remove |
987+
988+
#### Returns
989+
990+
| Type | Description |
991+
|------|-------------|
992+
| `boolean` | true if found and removed |
993+
994+
#### Example
995+
996+
```js
997+
peerStore.metadataBook.deleteValue(peerId, 'location')
998+
// false
999+
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
1000+
peerStore.metadataBook.deleteValue(peerId, 'location')
1001+
// true
1002+
```
1003+
1004+
### peerStore.metadataBook.get
1005+
1006+
Get the known metadata of a provided peer.
1007+
1008+
`peerStore.metadataBook.get(peerId)`
1009+
1010+
#### Parameters
1011+
1012+
| Name | Type | Description |
1013+
|------|------|-------------|
1014+
| peerId | [`PeerId`][peer-id] | peerId to get |
1015+
1016+
#### Returns
1017+
1018+
| Type | Description |
1019+
|------|-------------|
1020+
| `Map<string, Buffer>` | Peer Metadata |
1021+
1022+
#### Example
1023+
1024+
```js
1025+
peerStore.metadataBook.get(peerId)
1026+
// undefined
1027+
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
1028+
peerStore.metadataBook.get(peerId)
1029+
// Metadata Map
1030+
```
1031+
1032+
### peerStore.metadataBook.getValue
1033+
1034+
Get specific metadata of a provided peer.
1035+
1036+
`peerStore.metadataBook.getValue(peerId)`
1037+
1038+
#### Parameters
1039+
1040+
| Name | Type | Description |
1041+
|------|------|-------------|
1042+
| peerId | [`PeerId`][peer-id] | peerId to get |
1043+
| key | `string` | key of the metadata value to get |
1044+
1045+
#### Returns
1046+
1047+
| Type | Description |
1048+
|------|-------------|
1049+
| `Map<string, Buffer>` | Peer Metadata |
1050+
1051+
#### Example
1052+
1053+
```js
1054+
peerStore.metadataBook.getValue(peerId, 'location')
1055+
// undefined
1056+
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
1057+
peerStore.metadataBook.getValue(peerId, 'location')
1058+
// Metadata Map
1059+
```
1060+
1061+
### peerStore.metadataBook.set
1062+
1063+
Set known metadata of a given `peerId`.
1064+
1065+
`peerStore.metadataBook.set(peerId, key, value)`
1066+
1067+
#### Parameters
1068+
1069+
| Name | Type | Description |
1070+
|------|------|-------------|
1071+
| peerId | [`PeerId`][peer-id] | peerId to set |
1072+
| key | `string` | key of the metadata value to store |
1073+
| value | `Buffer` | metadata value to store |
1074+
1075+
#### Returns
1076+
1077+
| Type | Description |
1078+
|------|-------------|
1079+
| `MetadataBook` | Returns the Metadata Book component |
1080+
1081+
#### Example
1082+
1083+
```js
1084+
peerStore.metadataBook.set(peerId, 'location', Buffer.from('Berlin'))
1085+
```
1086+
9421087
### peerStore.protoBook.delete
9431088

9441089
Delete the provided peer from the book.

‎src/peer-store/README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ A `peerId.toB58String()` identifier mapping to a `Set` of protocol identifier st
7575

7676
#### Metadata Book
7777

78-
**Not Yet Implemented**
78+
The `metadataBook` keeps track of the known metadata of a peer. Its metadata is stored in a key value fashion, where a key identifier (`string`) represents a metadata value (`Buffer`).
79+
80+
`Map<string, Map<string, Buffer>>`
81+
82+
A `peerId.toB58String()` identifier mapping to the peer metadata Map.
7983

8084
### API
8185

@@ -85,13 +89,16 @@ Access to its underlying books:
8589

8690
- `peerStore.addressBook.*`
8791
- `peerStore.keyBook.*`
92+
- `peerStore.metadataBook.*`
8893
- `peerStore.protoBook.*`
8994

9095
### Events
9196

9297
- `peer` - emitted when a new peer is added.
9398
- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs.
9499
- `change:protocols` - emitted when a known peer supports a different set of protocols.
100+
- `change:pubkey` - emitted when a peer's public key is known.
101+
- `change:metadata` - emitted when known metadata of a peer changes.
95102

96103
## Data Persistence
97104

@@ -123,8 +130,6 @@ All public keys are stored under the following pattern:
123130

124131
**MetadataBook**
125132

126-
_NOT_YET_IMPLEMENTED_
127-
128133
Metadata is stored under the following key pattern:
129134

130135
`/peers/metadata/<b32 peer id no padding>/<key>`

‎src/peer-store/address-book.js

-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ class AddressBook extends Book {
9797
/**
9898
* Add known addresses of a provided peer.
9999
* If the peer is not known, it is set with the given addresses.
100-
* @override
101100
* @param {PeerId} peerId
102101
* @param {Array<Multiaddr>} multiaddrs
103102
* @returns {AddressBook}

‎src/peer-store/index.js

+24-27
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const PeerId = require('peer-id')
1010

1111
const AddressBook = require('./address-book')
1212
const KeyBook = require('./key-book')
13+
const MetadataBook = require('./metadata-book')
1314
const ProtoBook = require('./proto-book')
1415

1516
const {
@@ -21,6 +22,8 @@ const {
2122
* @fires PeerStore#peer Emitted when a new peer is added.
2223
* @fires PeerStore#change:protocols Emitted when a known peer supports a different set of protocols.
2324
* @fires PeerStore#change:multiaddrs Emitted when a known peer has a different set of multiaddrs.
25+
* @fires PeerStore#change:pubkey Emitted emitted when a peer's public key is known.
26+
* @fires PeerStore#change:metadata Emitted when the known metadata of a peer change.
2427
*/
2528
class PeerStore extends EventEmitter {
2629
/**
@@ -47,6 +50,11 @@ class PeerStore extends EventEmitter {
4750
*/
4851
this.keyBook = new KeyBook(this)
4952

53+
/**
54+
* MetadataBook containing a map of peerIdStr to their metadata Map.
55+
*/
56+
this.metadataBook = new MetadataBook(this)
57+
5058
/**
5159
* ProtoBook containing a map of peerIdStr to supported protocols.
5260
*/
@@ -68,31 +76,17 @@ class PeerStore extends EventEmitter {
6876
* @returns {Map<string, Peer>}
6977
*/
7078
get peers () {
71-
const peersData = new Map()
79+
const storedPeers = new Set([
80+
...this.addressBook.data.keys(),
81+
...this.keyBook.data.keys(),
82+
...this.protoBook.data.keys(),
83+
...this.metadataBook.data.keys()
84+
])
7285

73-
// AddressBook
74-
for (const [idStr, addresses] of this.addressBook.data.entries()) {
75-
const id = this.keyBook.data.get(idStr) || PeerId.createFromCID(idStr)
76-
peersData.set(idStr, {
77-
id,
78-
addresses,
79-
protocols: this.protoBook.get(id) || []
80-
})
81-
}
82-
83-
// ProtoBook
84-
for (const [idStr, protocols] of this.protoBook.data.entries()) {
85-
const pData = peersData.get(idStr)
86-
const id = this.keyBook.data.get(idStr) || PeerId.createFromCID(idStr)
87-
88-
if (!pData) {
89-
peersData.set(idStr, {
90-
id,
91-
addresses: [],
92-
protocols: Array.from(protocols)
93-
})
94-
}
95-
}
86+
const peersData = new Map()
87+
storedPeers.forEach((idStr) => {
88+
peersData.set(idStr, this.get(PeerId.createFromCID(idStr)))
89+
})
9690

9791
return peersData
9892
}
@@ -106,8 +100,9 @@ class PeerStore extends EventEmitter {
106100
const addressesDeleted = this.addressBook.delete(peerId)
107101
const keyDeleted = this.keyBook.delete(peerId)
108102
const protocolsDeleted = this.protoBook.delete(peerId)
103+
const metadataDeleted = this.metadataBook.delete(peerId)
109104

110-
return addressesDeleted || keyDeleted || protocolsDeleted
105+
return addressesDeleted || keyDeleted || protocolsDeleted || metadataDeleted
111106
}
112107

113108
/**
@@ -122,16 +117,18 @@ class PeerStore extends EventEmitter {
122117

123118
const id = this.keyBook.data.get(peerId.toB58String())
124119
const addresses = this.addressBook.get(peerId)
120+
const metadata = this.metadataBook.get(peerId)
125121
const protocols = this.protoBook.get(peerId)
126122

127-
if (!addresses && !protocols) {
123+
if (!id && !addresses && !metadata && !protocols) {
128124
return undefined
129125
}
130126

131127
return {
132128
id: id || peerId,
133129
addresses: addresses || [],
134-
protocols: protocols || []
130+
protocols: protocols || [],
131+
metadata: metadata
135132
}
136133
}
137134
}

‎src/peer-store/metadata-book.js

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'use strict'
2+
3+
const errcode = require('err-code')
4+
const debug = require('debug')
5+
const log = debug('libp2p:peer-store:proto-book')
6+
log.error = debug('libp2p:peer-store:proto-book:error')
7+
8+
const { Buffer } = require('buffer')
9+
10+
const PeerId = require('peer-id')
11+
12+
const Book = require('./book')
13+
14+
const {
15+
codes: { ERR_INVALID_PARAMETERS }
16+
} = require('../errors')
17+
18+
/**
19+
* The MetadataBook is responsible for keeping the known supported
20+
* protocols of a peer.
21+
* @fires MetadataBook#change:metadata
22+
*/
23+
class MetadataBook extends Book {
24+
/**
25+
* @constructor
26+
* @param {PeerStore} peerStore
27+
*/
28+
constructor (peerStore) {
29+
/**
30+
* PeerStore Event emitter, used by the MetadataBook to emit:
31+
* "change:metadata" - emitted when the known metadata of a peer change.
32+
*/
33+
super({
34+
peerStore,
35+
eventName: 'change:metadata',
36+
eventProperty: 'metadata'
37+
})
38+
39+
/**
40+
* Map known peers to their known protocols.
41+
* @type {Map<string, Map<string, Buffer>>}
42+
*/
43+
this.data = new Map()
44+
}
45+
46+
/**
47+
* Set metadata key and value of a provided peer.
48+
* @override
49+
* @param {PeerId} peerId
50+
* @param {string} key metadata key
51+
* @param {Buffer} value metadata value
52+
* @returns {ProtoBook}
53+
*/
54+
set (peerId, key, value) {
55+
if (!PeerId.isPeerId(peerId)) {
56+
log.error('peerId must be an instance of peer-id to store data')
57+
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
58+
}
59+
60+
if (typeof key !== 'string' || !Buffer.isBuffer(value)) {
61+
log.error('valid key and value must be provided to store data')
62+
throw errcode(new Error('valid key and value must be provided'), ERR_INVALID_PARAMETERS)
63+
}
64+
65+
this._setValue(peerId, key, value)
66+
67+
return this
68+
}
69+
70+
/**
71+
* Set data into the datastructure
72+
* @override
73+
*/
74+
_setValue (peerId, key, value, { emit = true } = {}) {
75+
const id = peerId.toB58String()
76+
const rec = this.data.get(id) || new Map()
77+
const recMap = rec.get(key)
78+
79+
// Already exists and is equal
80+
if (recMap && value.equals(recMap)) {
81+
log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`)
82+
return
83+
}
84+
85+
rec.set(key, value)
86+
this.data.set(id, rec)
87+
88+
emit && this._emit(peerId, key)
89+
}
90+
91+
/**
92+
* Get the known data of a provided peer.
93+
* @param {PeerId} peerId
94+
* @returns {Map<string, Buffer>}
95+
*/
96+
get (peerId) {
97+
if (!PeerId.isPeerId(peerId)) {
98+
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
99+
}
100+
101+
return this.data.get(peerId.toB58String())
102+
}
103+
104+
/**
105+
* Get specific metadata value, if it exists
106+
* @param {PeerId} peerId
107+
* @param {string} key
108+
* @returns {Buffer}
109+
*/
110+
getValue (peerId, key) {
111+
if (!PeerId.isPeerId(peerId)) {
112+
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
113+
}
114+
115+
const rec = this.data.get(peerId.toB58String())
116+
return rec && rec.get(key)
117+
}
118+
119+
/**
120+
* Deletes the provided peer from the book.
121+
* @param {PeerId} peerId
122+
* @returns {boolean}
123+
*/
124+
delete (peerId) {
125+
if (!PeerId.isPeerId(peerId)) {
126+
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
127+
}
128+
129+
if (!this.data.delete(peerId.toB58String())) {
130+
return false
131+
}
132+
133+
this._emit(peerId)
134+
135+
return true
136+
}
137+
138+
/**
139+
* Deletes the provided peer metadata key from the book.
140+
* @param {PeerId} peerId
141+
* @param {string} key
142+
* @returns {boolean}
143+
*/
144+
deleteValue (peerId, key) {
145+
if (!PeerId.isPeerId(peerId)) {
146+
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
147+
}
148+
149+
const rec = this.data.get(peerId.toB58String())
150+
151+
if (!rec || !rec.delete(key)) {
152+
return false
153+
}
154+
155+
this._emit(peerId, key)
156+
157+
return true
158+
}
159+
}
160+
161+
module.exports = MetadataBook

‎src/peer-store/persistent/consts.js

+3
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ module.exports.NAMESPACE_ADDRESS = '/peers/addrs/'
88
// /peers/keys/<b32 peer id no padding>
99
module.exports.NAMESPACE_KEYS = '/peers/keys/'
1010

11+
// /peers/metadata/<b32 peer id no padding>/<key>
12+
module.exports.NAMESPACE_METADATA = '/peers/metadata/'
13+
1114
// /peers/addrs/<b32 peer id no padding>
1215
module.exports.NAMESPACE_PROTOCOL = '/peers/protos/'

‎src/peer-store/persistent/index.js

+68
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
NAMESPACE_ADDRESS,
1515
NAMESPACE_COMMON,
1616
NAMESPACE_KEYS,
17+
NAMESPACE_METADATA,
1718
NAMESPACE_PROTOCOL
1819
} = require('./consts')
1920

@@ -43,6 +44,12 @@ class PersistentPeerStore extends PeerStore {
4344
*/
4445
this._dirtyPeers = new Set()
4546

47+
/**
48+
* Peers metadata changed mapping peer identifers to metadata changed.
49+
* @type {Map<string, Set<string>>}
50+
*/
51+
this._dirtyMetadata = new Map()
52+
4653
this.threshold = threshold
4754
this._addDirtyPeer = this._addDirtyPeer.bind(this)
4855
}
@@ -58,6 +65,7 @@ class PersistentPeerStore extends PeerStore {
5865
this.on('change:protocols', this._addDirtyPeer)
5966
this.on('change:multiaddrs', this._addDirtyPeer)
6067
this.on('change:pubkey', this._addDirtyPeer)
68+
this.on('change:metadata', this._addDirtyPeerMetadata)
6169

6270
// Load data
6371
for await (const entry of this._datastore.query({ prefix: NAMESPACE_COMMON })) {
@@ -92,6 +100,30 @@ class PersistentPeerStore extends PeerStore {
92100
}
93101
}
94102

103+
/**
104+
* Add modified metadata peer to the set.
105+
* @private
106+
* @param {Object} params
107+
* @param {PeerId} params.peerId
108+
* @param {string} params.metadata
109+
*/
110+
_addDirtyPeerMetadata ({ peerId, metadata }) {
111+
const peerIdstr = peerId.toB58String()
112+
113+
log('add dirty metadata peer', peerIdstr)
114+
this._dirtyPeers.add(peerIdstr)
115+
116+
// Add dirty metadata key
117+
const mData = this._dirtyMetadata.get(peerIdstr) || new Set()
118+
mData.add(metadata)
119+
this._dirtyMetadata.set(peerIdstr, mData)
120+
121+
if (this._dirtyPeers.size >= this.threshold) {
122+
// Commit current data
123+
this._commitData()
124+
}
125+
}
126+
95127
/**
96128
* Add all the peers current data to a datastore batch and commit it.
97129
* @private
@@ -120,6 +152,9 @@ class PersistentPeerStore extends PeerStore {
120152
// Key Book
121153
this._batchKeyBook(peerId, batch)
122154

155+
// Metadata Book
156+
this._batchMetadataBook(peerId, batch)
157+
123158
// Proto Book
124159
this._batchProtoBook(peerId, batch)
125160
}
@@ -184,6 +219,32 @@ class PersistentPeerStore extends PeerStore {
184219
}
185220
}
186221

222+
/**
223+
* Add metadata book data of the peer to the batch.
224+
* @private
225+
* @param {PeerId} peerId
226+
* @param {Object} batch
227+
*/
228+
_batchMetadataBook (peerId, batch) {
229+
const b32key = peerId.toString()
230+
const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || []
231+
232+
try {
233+
dirtyMetada.forEach((dirtyKey) => {
234+
const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`)
235+
const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey)
236+
237+
if (dirtyValue) {
238+
batch.put(key, dirtyValue)
239+
} else {
240+
batch.delete(key)
241+
}
242+
})
243+
} catch (err) {
244+
log.error(err)
245+
}
246+
}
247+
187248
/**
188249
* Add proto book data of the peer to the batch.
189250
* @private
@@ -244,6 +305,13 @@ class PersistentPeerStore extends PeerStore {
244305
decoded,
245306
{ emit: false })
246307
break
308+
case 'metadata':
309+
this.metadataBook._setValue(
310+
peerId,
311+
keyParts[4],
312+
value,
313+
{ emit: false })
314+
break
247315
case 'protos':
248316
decoded = Protocols.decode(value)
249317

‎src/peer-store/proto-book.js

-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ class ProtoBook extends Book {
8383
/**
8484
* Adds known protocols of a provided peer.
8585
* If the peer was not known before, it will be added.
86-
* @override
8786
* @param {PeerId} peerId
8887
* @param {Array<string>} protocols
8988
* @returns {ProtoBook}

‎test/peer-store/metadata-book.spec.js

+380
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
'use strict'
2+
/* eslint-env mocha */
3+
4+
const chai = require('chai')
5+
chai.use(require('dirty-chai'))
6+
chai.use(require('chai-bytes'))
7+
const { expect } = chai
8+
9+
const pDefer = require('p-defer')
10+
const PeerStore = require('../../src/peer-store')
11+
12+
const peerUtils = require('../utils/creators/peer')
13+
const {
14+
codes: { ERR_INVALID_PARAMETERS }
15+
} = require('../../src/errors')
16+
17+
describe('metadataBook', () => {
18+
let peerId
19+
20+
before(async () => {
21+
[peerId] = await peerUtils.createPeerId()
22+
})
23+
24+
describe('metadataBook.set', () => {
25+
let peerStore, mb
26+
27+
beforeEach(() => {
28+
peerStore = new PeerStore()
29+
mb = peerStore.metadataBook
30+
})
31+
32+
afterEach(() => {
33+
peerStore.removeAllListeners()
34+
})
35+
36+
it('throws invalid parameters error if invalid PeerId is provided', () => {
37+
try {
38+
mb.set('invalid peerId')
39+
} catch (err) {
40+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
41+
return
42+
}
43+
throw new Error('invalid peerId should throw error')
44+
})
45+
46+
it('throws invalid parameters error if no key provided', () => {
47+
try {
48+
mb.set(peerId)
49+
} catch (err) {
50+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
51+
return
52+
}
53+
throw new Error('no key provided should throw error')
54+
})
55+
56+
it('throws invalid parameters error if no value provided', () => {
57+
try {
58+
mb.set(peerId, 'location')
59+
} catch (err) {
60+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
61+
return
62+
}
63+
throw new Error('no value provided should throw error')
64+
})
65+
66+
it('throws invalid parameters error if value is not a buffer', () => {
67+
try {
68+
mb.set(peerId, 'location', 'mars')
69+
} catch (err) {
70+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
71+
return
72+
}
73+
throw new Error('invalid value provided should throw error')
74+
})
75+
76+
it('stores the content and emit change event', () => {
77+
const defer = pDefer()
78+
const metadataKey = 'location'
79+
const metadataValue = Buffer.from('mars')
80+
81+
peerStore.once('change:metadata', ({ peerId, metadata }) => {
82+
expect(peerId).to.exist()
83+
expect(metadata).to.equal(metadataKey)
84+
defer.resolve()
85+
})
86+
87+
mb.set(peerId, metadataKey, metadataValue)
88+
89+
const value = mb.getValue(peerId, metadataKey)
90+
expect(value).to.equalBytes(metadataValue)
91+
92+
const peerMetadata = mb.get(peerId)
93+
expect(peerMetadata).to.exist()
94+
expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue)
95+
96+
return defer.promise
97+
})
98+
99+
it('emits on set if not storing the exact same content', () => {
100+
const defer = pDefer()
101+
const metadataKey = 'location'
102+
const metadataValue1 = Buffer.from('mars')
103+
const metadataValue2 = Buffer.from('saturn')
104+
105+
let changeCounter = 0
106+
peerStore.on('change:metadata', () => {
107+
changeCounter++
108+
if (changeCounter > 1) {
109+
defer.resolve()
110+
}
111+
})
112+
113+
// set 1
114+
mb.set(peerId, metadataKey, metadataValue1)
115+
116+
// set 2 (same content)
117+
mb.set(peerId, metadataKey, metadataValue2)
118+
119+
const value = mb.getValue(peerId, metadataKey)
120+
expect(value).to.equalBytes(metadataValue2)
121+
122+
const peerMetadata = mb.get(peerId)
123+
expect(peerMetadata).to.exist()
124+
expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue2)
125+
126+
return defer.promise
127+
})
128+
129+
it('does not emit on set if it is storing the exact same content', () => {
130+
const defer = pDefer()
131+
const metadataKey = 'location'
132+
const metadataValue = Buffer.from('mars')
133+
134+
let changeCounter = 0
135+
peerStore.on('change:metadata', () => {
136+
changeCounter++
137+
if (changeCounter > 1) {
138+
defer.reject()
139+
}
140+
})
141+
142+
// set 1
143+
mb.set(peerId, metadataKey, metadataValue)
144+
145+
// set 2 (same content)
146+
mb.set(peerId, metadataKey, metadataValue)
147+
148+
// Wait 50ms for incorrect second event
149+
setTimeout(() => {
150+
defer.resolve()
151+
}, 50)
152+
153+
return defer.promise
154+
})
155+
})
156+
157+
describe('metadataBook.get', () => {
158+
let peerStore, mb
159+
160+
beforeEach(() => {
161+
peerStore = new PeerStore()
162+
mb = peerStore.metadataBook
163+
})
164+
165+
it('throws invalid parameters error if invalid PeerId is provided', () => {
166+
try {
167+
mb.get('invalid peerId')
168+
} catch (err) {
169+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
170+
return
171+
}
172+
throw new Error('invalid peerId should throw error')
173+
})
174+
175+
it('returns undefined if no metadata is known for the provided peer', () => {
176+
const metadata = mb.get(peerId)
177+
178+
expect(metadata).to.not.exist()
179+
})
180+
181+
it('returns the metadata stored', () => {
182+
const metadataKey = 'location'
183+
const metadataValue = Buffer.from('mars')
184+
185+
mb.set(peerId, metadataKey, metadataValue)
186+
187+
const peerMetadata = mb.get(peerId)
188+
expect(peerMetadata).to.exist()
189+
expect(peerMetadata.get(metadataKey)).to.equalBytes(metadataValue)
190+
})
191+
})
192+
193+
describe('metadataBook.getValue', () => {
194+
let peerStore, mb
195+
196+
beforeEach(() => {
197+
peerStore = new PeerStore()
198+
mb = peerStore.metadataBook
199+
})
200+
201+
it('throws invalid parameters error if invalid PeerId is provided', () => {
202+
try {
203+
mb.getValue('invalid peerId')
204+
} catch (err) {
205+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
206+
return
207+
}
208+
throw new Error('invalid peerId should throw error')
209+
})
210+
211+
it('returns undefined if no metadata is known for the provided peer', () => {
212+
const metadataKey = 'location'
213+
const metadata = mb.getValue(peerId, metadataKey)
214+
215+
expect(metadata).to.not.exist()
216+
})
217+
218+
it('returns the metadata value stored for the given key', () => {
219+
const metadataKey = 'location'
220+
const metadataValue = Buffer.from('mars')
221+
222+
mb.set(peerId, metadataKey, metadataValue)
223+
224+
const value = mb.getValue(peerId, metadataKey)
225+
expect(value).to.exist()
226+
expect(value).to.equalBytes(metadataValue)
227+
})
228+
229+
it('returns undefined if no metadata is known for the provided peer and key', () => {
230+
const metadataKey = 'location'
231+
const metadataBadKey = 'nickname'
232+
const metadataValue = Buffer.from('mars')
233+
234+
mb.set(peerId, metadataKey, metadataValue)
235+
236+
const metadata = mb.getValue(peerId, metadataBadKey)
237+
238+
expect(metadata).to.not.exist()
239+
})
240+
})
241+
242+
describe('metadataBook.delete', () => {
243+
let peerStore, mb
244+
245+
beforeEach(() => {
246+
peerStore = new PeerStore()
247+
mb = peerStore.metadataBook
248+
})
249+
250+
it('throwns invalid parameters error if invalid PeerId is provided', () => {
251+
try {
252+
mb.delete('invalid peerId')
253+
} catch (err) {
254+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
255+
return
256+
}
257+
throw new Error('invalid peerId should throw error')
258+
})
259+
260+
it('returns false if no records exist for the peer and no event is emitted', () => {
261+
const defer = pDefer()
262+
263+
peerStore.on('change:metadata', () => {
264+
defer.reject()
265+
})
266+
267+
const deleted = mb.delete(peerId)
268+
269+
expect(deleted).to.equal(false)
270+
271+
// Wait 50ms for incorrect invalid event
272+
setTimeout(() => {
273+
defer.resolve()
274+
}, 50)
275+
276+
return defer.promise
277+
})
278+
279+
it('returns true if the record exists and an event is emitted', () => {
280+
const defer = pDefer()
281+
const metadataKey = 'location'
282+
const metadataValue = Buffer.from('mars')
283+
284+
mb.set(peerId, metadataKey, metadataValue)
285+
286+
// Listen after set
287+
peerStore.on('change:metadata', () => {
288+
defer.resolve()
289+
})
290+
291+
const deleted = mb.delete(peerId)
292+
293+
expect(deleted).to.equal(true)
294+
295+
return defer.promise
296+
})
297+
})
298+
299+
describe('metadataBook.deleteValue', () => {
300+
let peerStore, mb
301+
302+
beforeEach(() => {
303+
peerStore = new PeerStore()
304+
mb = peerStore.metadataBook
305+
})
306+
307+
it('throws invalid parameters error if invalid PeerId is provided', () => {
308+
try {
309+
mb.deleteValue('invalid peerId')
310+
} catch (err) {
311+
expect(err.code).to.equal(ERR_INVALID_PARAMETERS)
312+
return
313+
}
314+
throw new Error('invalid peerId should throw error')
315+
})
316+
317+
it('returns false if no records exist for the peer and no event is emitted', () => {
318+
const defer = pDefer()
319+
const metadataKey = 'location'
320+
321+
peerStore.on('change:metadata', () => {
322+
defer.reject()
323+
})
324+
325+
const deleted = mb.deleteValue(peerId, metadataKey)
326+
327+
expect(deleted).to.equal(false)
328+
329+
// Wait 50ms for incorrect invalid event
330+
setTimeout(() => {
331+
defer.resolve()
332+
}, 50)
333+
334+
return defer.promise
335+
})
336+
337+
it('returns true if the record exists and an event is emitted', () => {
338+
const defer = pDefer()
339+
const metadataKey = 'location'
340+
const metadataValue = Buffer.from('mars')
341+
342+
mb.set(peerId, metadataKey, metadataValue)
343+
344+
// Listen after set
345+
peerStore.on('change:metadata', () => {
346+
defer.resolve()
347+
})
348+
349+
const deleted = mb.deleteValue(peerId, metadataKey)
350+
351+
expect(deleted).to.equal(true)
352+
353+
return defer.promise
354+
})
355+
356+
it('returns false if there is a record for the peer but not the given metadata key', () => {
357+
const defer = pDefer()
358+
const metadataKey = 'location'
359+
const metadataBadKey = 'nickname'
360+
const metadataValue = Buffer.from('mars')
361+
362+
mb.set(peerId, metadataKey, metadataValue)
363+
364+
peerStore.on('change:metadata', () => {
365+
defer.reject()
366+
})
367+
368+
const deleted = mb.deleteValue(peerId, metadataBadKey)
369+
370+
expect(deleted).to.equal(false)
371+
372+
// Wait 50ms for incorrect invalid event
373+
setTimeout(() => {
374+
defer.resolve()
375+
}, 50)
376+
377+
return defer.promise
378+
})
379+
})
380+
})

‎test/peer-store/peer-store.spec.js

+53
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,57 @@ describe('peer-store', () => {
158158
expect(peerListenint4[1].id.toB58String()).to.eql(peerIds[3].toB58String())
159159
})
160160
})
161+
162+
describe('peerStore.peers', () => {
163+
let peerStore
164+
165+
beforeEach(() => {
166+
peerStore = new PeerStore()
167+
})
168+
169+
it('returns peers if only addresses are known', () => {
170+
peerStore.addressBook.set(peerIds[0], [addr1])
171+
172+
const peers = peerStore.peers
173+
expect(peers.size).to.equal(1)
174+
175+
const peerData = peers.get(peerIds[0].toB58String())
176+
expect(peerData).to.exist()
177+
expect(peerData.id).to.exist()
178+
expect(peerData.addresses).to.have.lengthOf(1)
179+
expect(peerData.protocols).to.have.lengthOf(0)
180+
expect(peerData.metadata).to.not.exist()
181+
})
182+
183+
it('returns peers if only protocols are known', () => {
184+
peerStore.protoBook.set(peerIds[0], [proto1])
185+
186+
const peers = peerStore.peers
187+
expect(peers.size).to.equal(1)
188+
189+
const peerData = peers.get(peerIds[0].toB58String())
190+
expect(peerData).to.exist()
191+
expect(peerData.id).to.exist()
192+
expect(peerData.addresses).to.have.lengthOf(0)
193+
expect(peerData.protocols).to.have.lengthOf(1)
194+
expect(peerData.metadata).to.not.exist()
195+
})
196+
197+
it('returns peers if only metadata is known', () => {
198+
const metadataKey = 'location'
199+
const metadataValue = Buffer.from('earth')
200+
peerStore.metadataBook.set(peerIds[0], metadataKey, metadataValue)
201+
202+
const peers = peerStore.peers
203+
expect(peers.size).to.equal(1)
204+
205+
const peerData = peers.get(peerIds[0].toB58String())
206+
expect(peerData).to.exist()
207+
expect(peerData.id).to.exist()
208+
expect(peerData.addresses).to.have.lengthOf(0)
209+
expect(peerData.protocols).to.have.lengthOf(0)
210+
expect(peerData.metadata).to.exist()
211+
expect(peerData.metadata.get(metadataKey)).to.equalBytes(metadataValue)
212+
})
213+
})
161214
})

‎test/peer-store/persisted-peer-store.spec.js

+19-7
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ describe('Persisted PeerStore', () => {
118118
peerStore.protoBook.set(peers[0], protocols)
119119
peerStore.protoBook.set(peers[1], protocols)
120120

121-
expect(spyDs).to.have.property('callCount', 6) // 2 AddressBook + 2 KeyBook + 2 ProtoBook
121+
// MetadataBook
122+
peerStore.metadataBook.set(peers[0], 'location', Buffer.from('earth'))
123+
124+
expect(spyDs).to.have.property('callCount', 7) // 2 Address + 2 Key + 2 Proto + 1 Metadata
122125
expect(peerStore.peers.size).to.equal(2)
123126

124127
await peerStore.stop()
@@ -131,13 +134,14 @@ describe('Persisted PeerStore', () => {
131134

132135
await peerStore.start()
133136

134-
expect(spy).to.have.property('callCount', 6)
135-
expect(spyDs).to.have.property('callCount', 6)
137+
expect(spy).to.have.property('callCount', 7)
138+
expect(spyDs).to.have.property('callCount', 7)
136139

137140
expect(peerStore.peers.size).to.equal(2)
138141
expect(peerStore.addressBook.data.size).to.equal(2)
139142
expect(peerStore.keyBook.data.size).to.equal(2)
140143
expect(peerStore.protoBook.data.size).to.equal(2)
144+
expect(peerStore.metadataBook.data.size).to.equal(1)
141145
})
142146

143147
it('should delete content from the datastore on delete', async () => {
@@ -151,11 +155,14 @@ describe('Persisted PeerStore', () => {
151155
peerStore.addressBook.set(peer, multiaddrs)
152156
// ProtoBook
153157
peerStore.protoBook.set(peer, protocols)
158+
// MetadataBook
159+
peerStore.metadataBook.set(peer, 'location', Buffer.from('earth'))
154160

155161
const spyDs = sinon.spy(datastore, 'batch')
156162
const spyAddressBook = sinon.spy(peerStore.addressBook, 'delete')
157163
const spyKeyBook = sinon.spy(peerStore.keyBook, 'delete')
158164
const spyProtoBook = sinon.spy(peerStore.protoBook, 'delete')
165+
const spyMetadataBook = sinon.spy(peerStore.metadataBook, 'delete')
159166

160167
// Delete from PeerStore
161168
peerStore.delete(peer)
@@ -164,7 +171,8 @@ describe('Persisted PeerStore', () => {
164171
expect(spyAddressBook).to.have.property('callCount', 1)
165172
expect(spyKeyBook).to.have.property('callCount', 1)
166173
expect(spyProtoBook).to.have.property('callCount', 1)
167-
expect(spyDs).to.have.property('callCount', 2)
174+
expect(spyMetadataBook).to.have.property('callCount', 1)
175+
expect(spyDs).to.have.property('callCount', 3)
168176

169177
// Should have zero peer records stored in the datastore
170178
const queryParams = {
@@ -187,6 +195,7 @@ describe('Persisted PeerStore', () => {
187195

188196
it('should not commit until threshold is reached', async () => {
189197
const spyDirty = sinon.spy(peerStore, '_addDirtyPeer')
198+
const spyDirtyMetadata = sinon.spy(peerStore, '_addDirtyPeerMetadata')
190199
const spyDs = sinon.spy(datastore, 'batch')
191200

192201
const peers = await peerUtils.createPeerId({ number: 2 })
@@ -202,11 +211,13 @@ describe('Persisted PeerStore', () => {
202211
// Add Peer0 data in multiple books
203212
peerStore.addressBook.set(peers[0], multiaddrs)
204213
peerStore.protoBook.set(peers[0], protocols)
214+
peerStore.metadataBook.set(peers[0], 'location', Buffer.from('earth'))
205215

206216
// Remove data from the same Peer
207217
peerStore.addressBook.delete(peers[0])
208218

209219
expect(spyDirty).to.have.property('callCount', 3) // 2 AddrBook ops, 1 ProtoBook op
220+
expect(spyDirtyMetadata).to.have.property('callCount', 1) // 1 MetadataBook op
210221
expect(peerStore._dirtyPeers.size).to.equal(1)
211222
expect(spyDs).to.have.property('callCount', 0)
212223

@@ -221,14 +232,15 @@ describe('Persisted PeerStore', () => {
221232
peerStore.addressBook.set(peers[1], multiaddrs)
222233

223234
expect(spyDirty).to.have.property('callCount', 4)
235+
expect(spyDirtyMetadata).to.have.property('callCount', 1)
224236
expect(spyDs).to.have.property('callCount', 1)
225237

226-
// Should have two peer records stored in the datastore
238+
// Should have three peer records stored in the datastore
227239
let count = 0
228240
for await (const _ of datastore.query(queryParams)) { // eslint-disable-line
229241
count++
230242
}
231-
expect(count).to.equal(2)
243+
expect(count).to.equal(3)
232244
expect(peerStore.peers.size).to.equal(2)
233245
})
234246

@@ -241,7 +253,7 @@ describe('Persisted PeerStore', () => {
241253

242254
await peerStore.start()
243255

244-
// Add Peer data in a booka
256+
// Add Peer data in a book
245257
peerStore.protoBook.set(peer, protocols)
246258

247259
expect(spyDs).to.have.property('callCount', 0)

0 commit comments

Comments
 (0)
Please sign in to comment.