corestore 6.0.0-beta1 → 6.0.1-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/test/all.js CHANGED
@@ -1,302 +1,145 @@
1
- const p = require('path')
1
+ const test = require('brittle')
2
+ const crypto = require('hypercore-crypto')
2
3
  const ram = require('random-access-memory')
3
- const raf = require('random-access-file')
4
- const test = require('tape')
5
- const hypercoreCrypto = require('hypercore-crypto')
6
- const { toPromises } = require('hypercore-promisifier')
7
- const OldCorestore = require('@andrewosh/corestore-migration')
4
+ const tmp = require('tmp-promise')
8
5
 
9
6
  const Corestore = require('..')
10
- const { cleanup } = require('./helpers')
11
7
 
12
- test('ram-based corestore, acceptable get options', async t => {
13
- const store = create(ram)
8
+ test('basic get with caching', async function (t) {
9
+ const store = new Corestore(ram)
10
+ const core1a = store.get({ name: 'core-1' })
11
+ const core1b = store.get({ name: 'core-1' })
12
+ const core2 = store.get({ name: 'core-2' })
14
13
 
15
- // A name option
16
- const core1 = store.get({ name: 'default' })
17
- await toPromises(core1).ready()
14
+ await Promise.all([core1a.ready(), core1b.ready(), core2.ready()])
18
15
 
19
- {
20
- // Buffer arg
21
- const core = store.get(core1.key)
22
- await toPromises(core).ready()
23
- t.same(core, core1)
24
- }
16
+ t.alike(core1a.key, core1b.key)
17
+ t.unlike(core1a.key, core2.key)
25
18
 
26
- {
27
- // String arg
28
- const core = store.get(core1.key.toString('hex'))
29
- await toPromises(core).ready()
30
- t.same(core, core1)
31
- }
19
+ t.ok(core1a.writable)
20
+ t.ok(core1b.writable)
32
21
 
33
- {
34
- // Object arg
35
- const core = store.get({ key: core1.key })
36
- await toPromises(core).ready()
37
- t.same(core, core1)
38
- }
39
-
40
- {
41
- // Object arg with string key
42
- const core = store.get({ key: core1.key.toString('hex') })
43
- await toPromises(core).ready()
44
- t.same(core, core1)
45
- }
46
-
47
- {
48
- // Custom keypair
49
- const core = store.get({ keyPair: { secretKey: core1.secretKey, publicKey: core1.key } })
50
- await toPromises(core).ready()
51
- t.same(core, core1)
52
- }
53
-
54
- t.end()
22
+ t.is(store.cores.size, 2)
55
23
  })
56
24
 
57
- test('ram-based corestore, unacceptable get options', async t => {
58
- const store = create(ram)
59
- const badGets = [
60
- () => store.get(),
61
- () => store.get('abc'),
62
- () => store.get({ name: null }),
63
- () => store.get({ key: null })
64
- ]
65
- for (const get of badGets) {
66
- try {
67
- get()
68
- t.fail('get did not throw correctly')
69
- } catch (err) {
70
- t.true(err)
71
- }
72
- }
73
- })
74
-
75
- test('ram-based corestore, many gets before ready', async t => {
76
- const store = create(ram)
77
- const core1 = store.get({ name: 'core1' })
78
- const core2 = store.get({ name: 'core1' })
79
- const core3 = store.get({ name: 'core3' })
80
-
81
- await Promise.all([
82
- toPromises(core1).ready(),
83
- toPromises(core2).ready(),
84
- toPromises(core3).ready()
85
- ])
25
+ test('basic get with custom keypair', async function (t) {
26
+ const store = new Corestore(ram)
27
+ const kp1 = crypto.keyPair()
28
+ const kp2 = crypto.keyPair()
86
29
 
87
- t.same(core1, core2)
88
- t.notSame(core1, core3)
30
+ const core1 = store.get(kp1)
31
+ const core2 = store.get(kp2)
32
+ await Promise.all([core1.ready(), core2.ready()])
89
33
 
90
- // At this point, the pre-ready cores should've been moved to the main cache.
91
- t.same(store.cache.size, 2)
92
-
93
- t.end()
34
+ t.alike(core1.key, kp1.publicKey)
35
+ t.alike(core2.key, kp2.publicKey)
36
+ t.ok(core1.writable)
37
+ t.ok(core2.writable)
94
38
  })
95
39
 
96
- test('ram-based corestore, closing a ref decrements refcount', async t => {
97
- const store = create(ram)
98
- const core = toPromises(store.get({ name: 'core1' }))
99
- await core.ready()
40
+ test('basic namespaces', async function (t) {
41
+ const store = new Corestore(ram)
42
+ const ns1 = store.namespace('ns1')
43
+ const ns2 = store.namespace('ns2')
44
+ const ns3 = store.namespace('ns1') // Duplicate namespace
100
45
 
101
- const cacheEntry = store.cache.entries.get(core.discoveryKey.toString('hex'))
102
- t.same(cacheEntry.refs, 1)
46
+ const core1 = ns1.get({ name: 'main' })
47
+ const core2 = ns2.get({ name: 'main' })
48
+ const core3 = ns3.get({ name: 'main' })
49
+ await Promise.all([core1.ready(), core2.ready(), core3.ready()])
103
50
 
104
- await core.close()
105
- t.same(cacheEntry.refs, 0)
51
+ t.absent(core1.key.equals(core2.key))
52
+ t.ok(core1.key.equals(core3.key))
53
+ t.ok(core1.writable)
54
+ t.ok(core2.writable)
55
+ t.ok(core3.writable)
56
+ t.is(store.cores.size, 2)
106
57
 
107
58
  t.end()
108
59
  })
109
60
 
110
- test('ram-based corestore, simple replication', async t => {
111
- const store1 = create(ram)
112
- const store2 = create(ram)
61
+ test('basic replication', async function (t) {
62
+ const store1 = new Corestore(ram)
63
+ const store2 = new Corestore(ram)
113
64
 
114
- const core1 = toPromises(store1.get({ name: 'core1', valueEncoding: 'utf-8' }))
65
+ const core1 = store1.get({ name: 'core-1' })
66
+ const core2 = store1.get({ name: 'core-2' })
115
67
  await core1.append('hello')
116
-
117
- const s1 = store1.replicate(true, { live: true })
118
- s1.pipe(store2.replicate(false, { live: true })).pipe(s1)
119
-
120
- const clone1 = toPromises(store2.get({ key: core1.key, valueEncoding: 'utf-8' }))
121
- t.same(await clone1.get(0), 'hello')
122
-
123
- const core2 = toPromises(store1.get({ name: 'core2', valueEncoding: 'utf-8' }))
124
68
  await core2.append('world')
125
69
 
126
- const clone2 = toPromises(store2.get({ key: core2.key, valueEncoding: 'utf-8' }))
127
- t.same(await clone2.get(0), 'world')
128
-
129
- t.end()
130
- })
131
-
132
- test('ram-based corestore, sparse replication', async t => {
133
- const store1 = create(ram, { sparse: true })
134
- const store2 = create(ram, { sparse: true })
135
-
136
- const core1 = toPromises(store1.get({ name: 'core1', valueEncoding: 'utf-8' }))
137
- await core1.append('hello')
138
-
139
- const s1 = store1.replicate(true, { live: true })
140
- s1.pipe(store2.replicate(false, { live: true })).pipe(s1)
141
-
142
- const clone1 = toPromises(store2.get({ key: core1.key, valueEncoding: 'utf-8' }))
143
- t.same(await clone1.get(0), 'hello')
144
-
145
- const core2 = toPromises(store1.get({ name: 'core2', valueEncoding: 'utf-8' }))
146
- await core2.append('world')
70
+ const core3 = store2.get({ key: core1.key })
71
+ const core4 = store2.get({ key: core2.key })
147
72
 
148
- const clone2 = toPromises(store2.get({ key: core2.key, valueEncoding: 'utf-8' }))
149
- t.same(await clone2.get(0), 'world')
73
+ const s = store1.replicate(true)
74
+ s.pipe(store2.replicate(false)).pipe(s)
150
75
 
151
- t.end()
76
+ t.alike(await core3.get(0), Buffer.from('hello'))
77
+ t.alike(await core4.get(0), Buffer.from('world'))
152
78
  })
153
79
 
154
- test('raf-based corestore, simple replication', async t => {
155
- const store1 = create(path => raf(p.join('store1', path)))
156
- const store2 = create(path => raf(p.join('store2', path)))
157
-
158
- const core1 = toPromises(store1.get({ name: 'core1', valueEncoding: 'utf-8' }))
159
- await core1.append('hello')
160
-
161
- const s1 = store1.replicate(true, { live: true })
162
- s1.pipe(store2.replicate(false, { live: true })).pipe(s1)
80
+ test('nested namespaces', async function (t) {
81
+ const store = new Corestore(ram)
82
+ const ns1a = store.namespace('ns1').namespace('a')
83
+ const ns1b = store.namespace('ns1').namespace('b')
163
84
 
164
- const clone1 = toPromises(store2.get({ key: core1.key, valueEncoding: 'utf-8' }))
165
- t.same(await clone1.get(0), 'hello')
85
+ const core1 = ns1a.get({ name: 'main' })
86
+ const core2 = ns1b.get({ name: 'main' })
87
+ await Promise.all([core1.ready(), core2.ready()])
166
88
 
167
- const core2 = toPromises(store1.get({ name: 'core2', valueEncoding: 'utf-8' }))
168
- await core2.append('world')
169
-
170
- const clone2 = toPromises(store2.get({ key: core2.key, valueEncoding: 'utf-8' }))
171
- t.same(await clone2.get(0), 'world')
172
-
173
- await cleanup(['store1', 'store2'])
174
- t.end()
89
+ t.not(core1.key.equals(core2.key))
90
+ t.ok(core1.writable)
91
+ t.ok(core2.writable)
92
+ t.is(store.cores.size, 2)
175
93
  })
176
94
 
177
- test('raf-based corestore, close and reopen', async t => {
178
- let store = create('test-store')
179
-
180
- let core1 = toPromises(store.get({ name: 'core1', valueEncoding: 'utf-8' }))
181
- await core1.append('hello')
182
-
183
- t.same(await core1.get(0), 'hello')
184
-
185
- await store.close()
186
- store = create('test-store')
187
- core1 = toPromises(store.get({ name: 'core1', valueEncoding: 'utf-8' }))
188
-
189
- t.same(await core1.get(0), 'hello')
190
-
191
- await cleanup(['test-store'])
192
- t.end()
95
+ test('core uncached when all sessions close', async function (t) {
96
+ const store = new Corestore(ram)
97
+ const core1 = store.get({ name: 'main' })
98
+ await core1.ready()
99
+ t.is(store.cores.size, 1)
100
+ await core1.close()
101
+ t.is(store.cores.size, 0)
193
102
  })
194
103
 
195
- test('raf-based corestore, close and reopen with keypair option', async t => {
196
- let store = create('test-store')
197
- const keyPair = hypercoreCrypto.keyPair()
104
+ test('writable core loaded from name userData', async function (t) {
105
+ const dir = await tmp.dir({ unsafeCleanup: true })
198
106
 
199
- let core1 = toPromises(store.get({ keyPair, valueEncoding: 'utf-8' }))
200
- await core1.append('hello')
107
+ let store = new Corestore(dir.path)
108
+ let core = store.get({ name: 'main' })
109
+ await core.ready()
110
+ const key = core.key
201
111
 
202
- t.same(await core1.get(0), 'hello')
112
+ t.ok(core.writable)
113
+ await core.append('hello')
114
+ t.is(core.length, 1)
203
115
 
204
116
  await store.close()
205
- store = create('test-store')
206
- core1 = toPromises(store.get({ keyPair, valueEncoding: 'utf-8' }))
207
-
208
- t.same(await core1.get(0), 'hello')
209
-
210
- await cleanup(['test-store'])
211
- t.end()
212
- })
213
-
214
- test('namespace method is equivalent to name array', async t => {
215
- const store = create(ram)
216
-
217
- const core1 = store.get({ name: ['a', 'b', 'c'] })
218
- const core2 = store.namespace('a').namespace('b').get({ name: 'c' })
219
-
220
- await toPromises(core1).ready()
221
- await toPromises(core2).ready()
222
-
223
- t.same(core1, core2)
224
- t.end()
225
- })
226
-
227
- test('can backup/restore', async t => {
228
- const firstStore = create(ram)
229
- const core1 = firstStore.get({ name: 'hello-world' })
230
- await toPromises(core1).ready()
231
- const manifest = await firstStore.backup()
232
-
233
- const secondStore = await Corestore.restore(manifest, ram)
234
- const core2 = secondStore.get({ key: core1.key })
235
- await toPromises(core2).ready()
117
+ store = new Corestore(dir.path)
118
+ core = store.get(key)
119
+ await core.ready()
236
120
 
237
- t.true(core2.writable)
121
+ t.ok(core.writable)
122
+ await core.append('world')
123
+ t.is(core.length, 2)
124
+ t.alike(await core.get(0), Buffer.from('hello'))
125
+ t.alike(await core.get(1), Buffer.from('world'))
238
126
 
239
- t.end()
127
+ await dir.cleanup()
240
128
  })
241
129
 
242
- test('can migrate an old corestore', async t => {
243
- const oldStore = new OldCorestore('test-store')
244
- await oldStore.ready()
245
-
246
- const oldCores = [
247
- oldStore.default(),
248
- oldStore.namespace('hello').default(),
249
- oldStore.namespace('hello').namespace('world').default(),
250
- oldStore.get({ name: 'test-name' }),
251
- // Randomly-generated names should also be migrated.
252
- oldStore.get()
253
- ]
254
- await Promise.all(oldCores.map(c => new Promise(resolve => c.ready(resolve))))
255
-
256
- const names = [
257
- 'default',
258
- ['hello'],
259
- ['hello', 'world'],
260
- 'test-name'
261
- ]
262
- const keys = oldCores.map(c => c.key)
263
- await new Promise(resolve => oldStore.close(resolve))
264
-
265
- {
266
- const store = create('test-store', {
267
- migrationRoot: 'test-store'
268
- })
269
-
270
- // Cores are writable when loaded by name
271
- for (let i = 0; i < names.length; i++) {
272
- const core = store.get({ name: names[i] })
273
- await toPromises(core).ready()
274
- t.true(core.writable)
275
- t.same(core.key, keys[i])
276
- }
277
-
278
- await store.close()
279
- }
280
-
281
- {
282
- // The second time should work without a migration.
283
- const store = create('test-store')
130
+ test('storage locking', async function (t) {
131
+ const dir = await tmp.dir({ unsafeCleanup: true })
284
132
 
285
- // Cores are writable when loaded by key
286
- for (let i = 0; i < keys.length; i++) {
287
- const core = store.get({ key: keys[i] })
288
- await toPromises(core).ready()
289
- t.true(core.writable)
290
- }
133
+ const store1 = new Corestore(dir.path)
134
+ await store1.ready()
291
135
 
292
- await store.close()
136
+ const store2 = new Corestore(dir.path)
137
+ try {
138
+ await store2.ready()
139
+ t.fail('dir should have been locked')
140
+ } catch {
141
+ t.pass('dir was locked')
293
142
  }
294
143
 
295
- await cleanup(['test-store'])
296
- t.end()
144
+ await dir.cleanup()
297
145
  })
298
-
299
- function create (storage, opts) {
300
- const store = new Corestore(storage, opts)
301
- return store
302
- }
@@ -1,12 +1,7 @@
1
- const fs = require('fs')
1
+ const fs = require('fs').promises
2
2
 
3
3
  async function cleanup (dirs) {
4
- return Promise.all(dirs.map(dir => new Promise((resolve, reject) => {
5
- fs.rmdir(dir, { recursive: true }, err => {
6
- if (err) return reject(err)
7
- return resolve()
8
- })
9
- })))
4
+ return Promise.allSettled(dirs.map(dir => fs.rmdir(dir, { recursive: true })))
10
5
  }
11
6
 
12
7
  function delay (ms, cb) {
package/test/keys.js ADDED
@@ -0,0 +1,65 @@
1
+ const p = require('path')
2
+ const fs = require('fs')
3
+
4
+ const test = require('brittle')
5
+ const ram = require('random-access-memory')
6
+ const raf = require('random-access-file')
7
+
8
+ const KeyManager = require('../lib/keys')
9
+
10
+ test('can create hypercore keypairs', async t => {
11
+ const keys = await KeyManager.fromStorage(ram)
12
+
13
+ const kp1 = await keys.createHypercoreKeyPair('core1')
14
+ const kp2 = await keys.createHypercoreKeyPair('core2')
15
+
16
+ t.is(kp1.publicKey.length, 32)
17
+ t.is(kp2.publicKey.length, 32)
18
+ t.unlike(kp1.publicKey, kp2.publicKey)
19
+ })
20
+
21
+ test('distinct tokens create distinct hypercore keypairs', async t => {
22
+ const keys = await KeyManager.fromStorage(ram)
23
+ const token1 = KeyManager.createToken()
24
+ const token2 = KeyManager.createToken()
25
+
26
+ const kp1 = await keys.createHypercoreKeyPair('core1', token1)
27
+ const kp2 = await keys.createHypercoreKeyPair('core1', token2)
28
+
29
+ t.unlike(kp1.publicKey, kp2.publicKey)
30
+ })
31
+
32
+ test('short user-provided token will throw', async t => {
33
+ const keys = await KeyManager.fromStorage(ram)
34
+
35
+ try {
36
+ await keys.createHypercoreKeyPair('core1', Buffer.from('hello'))
37
+ t.fail('did not throw')
38
+ } catch {
39
+ t.pass('threw correctly')
40
+ }
41
+ })
42
+
43
+ test('persistent storage regenerates keys correctly', async t => {
44
+ const testPath = p.resolve(__dirname, 'test-data')
45
+
46
+ const keys1 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
47
+ const kp1 = await keys1.createHypercoreKeyPair('core1')
48
+
49
+ const keys2 = await KeyManager.fromStorage((name) => raf(testPath, { directory: testPath }))
50
+ const kp2 = await keys2.createHypercoreKeyPair('core1')
51
+
52
+ t.alike(kp1.publicKey, kp2.publicKey)
53
+
54
+ await fs.promises.rm(testPath, { recursive: true })
55
+ })
56
+
57
+ test('different master keys -> different keys', async t => {
58
+ const keys1 = await KeyManager.fromStorage(ram)
59
+ const keys2 = await KeyManager.fromStorage(ram)
60
+
61
+ const kp1 = await keys1.createHypercoreKeyPair('core1')
62
+ const kp2 = await keys2.createHypercoreKeyPair('core1')
63
+
64
+ t.unlike(kp1.publicKey, kp2.publicKey)
65
+ })
@@ -1,26 +0,0 @@
1
- const RAS = require('random-access-storage')
2
-
3
- module.exports = class BufferFile extends RAS {
4
- constructor (buf) {
5
- super()
6
- this._buf = buf
7
- }
8
-
9
- _open (req) {
10
- if (typeof this._buf !== 'function') return req.callback(null)
11
- this._buf((err, b) => {
12
- if (err) return req.callback(err)
13
- this._buf = b
14
- req.callback(null)
15
- })
16
- }
17
-
18
- _stat (req) {
19
- req.callback(null, { size: this._buf ? this._buf.length : 0 })
20
- }
21
-
22
- _read (req) {
23
- if (!this._buf) return req.callback(new Error('Could not satisfy length'))
24
- req.callback(null, this._buf.slice(req.offset, req.offset + req.size))
25
- }
26
- }
package/lib/db.js DELETED
@@ -1,160 +0,0 @@
1
- const p = require('path')
2
- const fs = require('fs').promises
3
-
4
- const varint = require('varint')
5
- const Hyperbee = require('hyperbee')
6
- const hypercore = require('hypercore')
7
- const hypercoreCrypto = require('hypercore-crypto')
8
- const readdir = require('@jsdevtools/readdir-enhanced')
9
- const { NanoresourcePromise: Nanoresource } = require('nanoresource-promise/emitter')
10
-
11
- const INDEX_PATH = 'index'
12
- const INDEX_VERSION = '@corestore/v1'
13
- const MIGRATION_KEY = '@migrated'
14
- const KEYS_NAMESPACE = 'by-key'
15
- const DKEYS_NAMESPACE = 'by-dkey'
16
-
17
- module.exports = class Index extends Nanoresource {
18
- constructor (storage, opts = {}) {
19
- super()
20
-
21
- this.storage = storage
22
- this._migrationRoot = opts.migrationRoot
23
- this._core = hypercore(p => this.storage(INDEX_PATH + '/' + p))
24
- this._db = new Hyperbee(this._core, {
25
- keyEncoding: 'utf-8',
26
- valueEncoding: 'json'
27
- }).sub(INDEX_VERSION)
28
-
29
- this._byKey = this._db.sub(KEYS_NAMESPACE)
30
- this._byDKey = this._db.sub(DKEYS_NAMESPACE)
31
-
32
- this._core.on('error', err => this.emit('error', err))
33
- }
34
-
35
- // Nanoresource Methods
36
-
37
- async _open () {
38
- await new Promise((resolve, reject) => {
39
- this._core.open(err => {
40
- if (err) return reject(err)
41
- return resolve()
42
- })
43
- })
44
- return this._maybeMigrate()
45
- }
46
-
47
- _close () {
48
- return new Promise((resolve, reject) => {
49
- this._core.close(err => {
50
- if (err) return reject(err)
51
- return resolve()
52
- })
53
- })
54
- }
55
-
56
- // Private Methods
57
-
58
- async _maybeMigrate () {
59
- if (!this._migrationRoot) return
60
- const isMigrated = await this._db.get(MIGRATION_KEY)
61
- if (isMigrated) return
62
-
63
- const basePath = p.resolve(this._migrationRoot)
64
- const nameIterator = readdir.iterator(basePath, {
65
- basePath,
66
- deep: true,
67
- filter: /.*name$/
68
- })
69
-
70
- const b = this._db.batch()
71
- await b.put(MIGRATION_KEY, { migrated: true })
72
-
73
- for await (const namePath of nameIterator) {
74
- let name = await fs.readFile(namePath)
75
- if (!varint.decode(name)) continue
76
- name = name.slice(varint.decode.bytes).toString('utf-8')
77
-
78
- const keyPath = p.join(p.dirname(namePath), 'key')
79
- const publicKey = await loadKey(keyPath)
80
- if (!publicKey) continue
81
-
82
- const discoveryKey = hypercoreCrypto.discoveryKey(publicKey)
83
- await this._putCoreBatch({
84
- name,
85
- publicKey,
86
- discoveryKey
87
- }, b)
88
- }
89
-
90
- return b.flush()
91
-
92
- async function loadKey (path) {
93
- try {
94
- const key = await fs.readFile(path)
95
- return key
96
- } catch (err) {
97
- if (err.code !== 'ENOENT') throw err
98
- return null
99
- }
100
- }
101
- }
102
-
103
- async _putCoreBatch (keys, batch) {
104
- const record = {
105
- name: keys.name,
106
- publicKey: toString(keys.publicKey),
107
- discoveryKey: toString(keys.discoveryKey)
108
- }
109
- const b = batch || this._db.batch()
110
- await b.put(KEYS_NAMESPACE + this._db.sep + record.publicKey, record)
111
- await b.put(DKEYS_NAMESPACE + this._db.sep + record.discoveryKey, record)
112
- if (!batch) await b.flush()
113
- }
114
-
115
- // Public Methods
116
-
117
- async getName (keys) {
118
- const node = await this._byKey.get(toString(keys.publicKey))
119
- return node && node.value.name
120
- }
121
-
122
- async getPassiveCoreKeys (dkey) {
123
- const node = await this._byDKey.get(toString(dkey))
124
- return node && toKeys(node.value)
125
- }
126
-
127
- async getAllCores () {
128
- const allCores = []
129
- for await (const { value } of this._byKey.createReadStream()) {
130
- allCores.push(value)
131
- }
132
- return allCores
133
- }
134
-
135
- async saveKeys (keys) {
136
- const existing = await this.getName(keys)
137
- if (existing) return
138
- return this._putCoreBatch(keys)
139
- }
140
-
141
- async restore (manifest) {
142
- if (!manifest.cores || !Array.isArray(manifest.cores)) throw new Error('Malformed manifest.')
143
- for (const keys of manifest.cores) {
144
- await this.saveKeys(keys)
145
- }
146
- }
147
- }
148
-
149
- function toString (buf) {
150
- if (typeof buf === 'string') return buf
151
- return buf.toString('hex')
152
- }
153
-
154
- function toKeys (record) {
155
- return {
156
- name: record.name,
157
- publicKey: Buffer.from(record.publicKey, 'hex'),
158
- discoveryKey: Buffer.from(record.discoveryKey, 'hex')
159
- }
160
- }