@libp2p/kad-dht 15.0.2 → 15.1.0
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/dist/index.min.js +2 -15
- package/dist/index.min.js.map +7 -0
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/content-fetching/index.d.ts +4 -4
- package/dist/src/content-fetching/index.d.ts.map +1 -1
- package/dist/src/content-fetching/index.js +39 -12
- package/dist/src/content-fetching/index.js.map +1 -1
- package/dist/src/content-routing/index.d.ts.map +1 -1
- package/dist/src/content-routing/index.js +87 -37
- package/dist/src/content-routing/index.js.map +1 -1
- package/dist/src/index.d.ts +57 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/kad-dht.d.ts +3 -1
- package/dist/src/kad-dht.d.ts.map +1 -1
- package/dist/src/kad-dht.js +14 -10
- package/dist/src/kad-dht.js.map +1 -1
- package/dist/src/network.d.ts +12 -6
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +17 -16
- package/dist/src/network.js.map +1 -1
- package/dist/src/peer-distance-list.d.ts +10 -3
- package/dist/src/peer-distance-list.d.ts.map +1 -1
- package/dist/src/peer-distance-list.js +13 -5
- package/dist/src/peer-distance-list.js.map +1 -1
- package/dist/src/peer-routing/index.d.ts +10 -8
- package/dist/src/peer-routing/index.d.ts.map +1 -1
- package/dist/src/peer-routing/index.js +78 -48
- package/dist/src/peer-routing/index.js.map +1 -1
- package/dist/src/providers.d.ts +4 -4
- package/dist/src/providers.d.ts.map +1 -1
- package/dist/src/providers.js +12 -12
- package/dist/src/providers.js.map +1 -1
- package/dist/src/query/events.d.ts +15 -3
- package/dist/src/query/events.d.ts.map +1 -1
- package/dist/src/query/events.js +9 -0
- package/dist/src/query/events.js.map +1 -1
- package/dist/src/query/manager.d.ts +2 -4
- package/dist/src/query/manager.d.ts.map +1 -1
- package/dist/src/query/manager.js +42 -16
- package/dist/src/query/manager.js.map +1 -1
- package/dist/src/query/query-path.d.ts +10 -14
- package/dist/src/query/query-path.d.ts.map +1 -1
- package/dist/src/query/query-path.js +67 -53
- package/dist/src/query/query-path.js.map +1 -1
- package/dist/src/query/types.d.ts +6 -5
- package/dist/src/query/types.d.ts.map +1 -1
- package/dist/src/query-self.d.ts +2 -3
- package/dist/src/query-self.d.ts.map +1 -1
- package/dist/src/query-self.js +11 -14
- package/dist/src/query-self.js.map +1 -1
- package/dist/src/routing-table/closest-peers.js +1 -1
- package/dist/src/routing-table/closest-peers.js.map +1 -1
- package/dist/src/routing-table/k-bucket.js +1 -1
- package/dist/src/routing-table/k-bucket.js.map +1 -1
- package/dist/src/rpc/handlers/get-providers.js +1 -1
- package/dist/src/rpc/handlers/get-providers.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +4 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/typedoc-urls.json +4 -0
- package/package.json +12 -11
- package/src/constants.ts +1 -1
- package/src/content-fetching/index.ts +40 -13
- package/src/content-routing/index.ts +92 -44
- package/src/index.ts +64 -5
- package/src/kad-dht.ts +14 -10
- package/src/network.ts +33 -20
- package/src/peer-distance-list.ts +19 -7
- package/src/peer-routing/index.ts +83 -52
- package/src/providers.ts +13 -13
- package/src/query/events.ts +27 -3
- package/src/query/manager.ts +48 -21
- package/src/query/query-path.ts +86 -76
- package/src/query/types.ts +7 -5
- package/src/query-self.ts +15 -17
- package/src/routing-table/closest-peers.ts +1 -1
- package/src/routing-table/k-bucket.ts +1 -1
- package/src/rpc/handlers/get-providers.ts +1 -1
- package/src/utils.ts +9 -0
package/src/query/query-path.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { setMaxListeners } from '@libp2p/interface'
|
|
2
1
|
import { Queue } from '@libp2p/utils/queue'
|
|
3
|
-
import {
|
|
2
|
+
import { pushable } from 'it-pushable'
|
|
4
3
|
import { xor as uint8ArrayXor } from 'uint8arrays/xor'
|
|
5
4
|
import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
|
|
6
5
|
import { QueryAbortedError } from '../errors.js'
|
|
7
6
|
import { convertPeerId, convertBuffer } from '../utils.js'
|
|
8
|
-
import { queryErrorEvent } from './events.js'
|
|
7
|
+
import { pathEndedEvent, queryErrorEvent } from './events.js'
|
|
9
8
|
import type { QueryEvent } from '../index.js'
|
|
10
9
|
import type { QueryFunc } from '../query/types.js'
|
|
11
|
-
import type { Logger, PeerId, RoutingOptions, AbortOptions } from '@libp2p/interface'
|
|
10
|
+
import type { Logger, PeerId, RoutingOptions, AbortOptions, PeerInfo } from '@libp2p/interface'
|
|
12
11
|
import type { ConnectionManager } from '@libp2p/interface-internal'
|
|
13
|
-
import type {
|
|
12
|
+
import type { Filter } from '@libp2p/utils/filters'
|
|
14
13
|
|
|
15
14
|
export interface QueryPathOptions extends RoutingOptions {
|
|
16
15
|
/**
|
|
@@ -21,18 +20,13 @@ export interface QueryPathOptions extends RoutingOptions {
|
|
|
21
20
|
/**
|
|
22
21
|
* Where we start our query
|
|
23
22
|
*/
|
|
24
|
-
|
|
23
|
+
startingPeers: PeerId[]
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* Who we are
|
|
28
27
|
*/
|
|
29
28
|
ourPeerId: PeerId
|
|
30
29
|
|
|
31
|
-
/**
|
|
32
|
-
* When to stop querying
|
|
33
|
-
*/
|
|
34
|
-
signal: AbortSignal
|
|
35
|
-
|
|
36
30
|
/**
|
|
37
31
|
* The query function to run with each peer
|
|
38
32
|
*/
|
|
@@ -44,20 +38,15 @@ export interface QueryPathOptions extends RoutingOptions {
|
|
|
44
38
|
alpha: number
|
|
45
39
|
|
|
46
40
|
/**
|
|
47
|
-
*
|
|
41
|
+
* The index within `k` this path represents
|
|
48
42
|
*/
|
|
49
|
-
|
|
43
|
+
path: number
|
|
50
44
|
|
|
51
45
|
/**
|
|
52
|
-
* How many
|
|
46
|
+
* How many disjoint paths are in this query
|
|
53
47
|
*/
|
|
54
48
|
numPaths: number
|
|
55
49
|
|
|
56
|
-
/**
|
|
57
|
-
* A timeout for queryFunc in ms
|
|
58
|
-
*/
|
|
59
|
-
queryFuncTimeout?: number
|
|
60
|
-
|
|
61
50
|
/**
|
|
62
51
|
* Query log
|
|
63
52
|
*/
|
|
@@ -66,12 +55,17 @@ export interface QueryPathOptions extends RoutingOptions {
|
|
|
66
55
|
/**
|
|
67
56
|
* Set of peers seen by this and other paths
|
|
68
57
|
*/
|
|
69
|
-
peersSeen:
|
|
58
|
+
peersSeen: Filter
|
|
70
59
|
|
|
71
60
|
/**
|
|
72
61
|
* The libp2p connection manager
|
|
73
62
|
*/
|
|
74
63
|
connectionManager: ConnectionManager
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The overall query abort signal
|
|
67
|
+
*/
|
|
68
|
+
signal: AbortSignal
|
|
75
69
|
}
|
|
76
70
|
|
|
77
71
|
interface QueryQueueOptions extends AbortOptions {
|
|
@@ -83,60 +77,75 @@ interface QueryQueueOptions extends AbortOptions {
|
|
|
83
77
|
* every peer encountered that we have not seen before
|
|
84
78
|
*/
|
|
85
79
|
export async function * queryPath (options: QueryPathOptions): AsyncGenerator<QueryEvent, void, undefined> {
|
|
86
|
-
const { key,
|
|
80
|
+
const { key, startingPeers, ourPeerId, query, alpha, path, numPaths, log, peersSeen, connectionManager, signal } = options
|
|
81
|
+
const events = pushable<QueryEvent>({
|
|
82
|
+
objectMode: true
|
|
83
|
+
})
|
|
84
|
+
|
|
87
85
|
// Only ALPHA node/value lookups are allowed at any given time for each process
|
|
88
86
|
// https://github.com/libp2p/specs/tree/master/kad-dht#alpha-concurrency-parameter-%CE%B1
|
|
89
|
-
const queue = new Queue<
|
|
87
|
+
const queue = new Queue<undefined, QueryQueueOptions>({
|
|
90
88
|
concurrency: alpha,
|
|
91
89
|
sort: (a, b) => uint8ArrayXorCompare(a.options.distance, b.options.distance)
|
|
92
90
|
})
|
|
91
|
+
queue.addEventListener('idle', () => {
|
|
92
|
+
events.push(pathEndedEvent({
|
|
93
|
+
path: {
|
|
94
|
+
index: path,
|
|
95
|
+
queued: queue.queued,
|
|
96
|
+
running: queue.running,
|
|
97
|
+
total: queue.size
|
|
98
|
+
}
|
|
99
|
+
}, options))
|
|
100
|
+
|
|
101
|
+
events.end()
|
|
102
|
+
})
|
|
103
|
+
queue.addEventListener('error', (evt) => {
|
|
104
|
+
log.error('error during query - %e', evt.detail)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
signal.addEventListener('abort', () => {
|
|
108
|
+
queue.abort()
|
|
109
|
+
events.end(new QueryAbortedError())
|
|
110
|
+
})
|
|
93
111
|
|
|
94
112
|
// perform lookups on kadId, not the actual value
|
|
95
113
|
const kadId = await convertBuffer(key)
|
|
96
114
|
|
|
97
115
|
/**
|
|
98
|
-
* Adds the passed peer to the query queue if it's not us and no
|
|
99
|
-
*
|
|
116
|
+
* Adds the passed peer to the query queue if it's not us and no other path
|
|
117
|
+
* has passed through this peer
|
|
100
118
|
*/
|
|
101
|
-
function queryPeer (peer:
|
|
119
|
+
function queryPeer (peer: PeerInfo, peerKadId: Uint8Array): void {
|
|
102
120
|
if (peer == null) {
|
|
103
121
|
return
|
|
104
122
|
}
|
|
105
123
|
|
|
106
|
-
peersSeen.add(peer)
|
|
124
|
+
peersSeen.add(peer.id.toMultihash().bytes)
|
|
107
125
|
|
|
108
126
|
const peerXor = uint8ArrayXor(peerKadId, kadId)
|
|
109
127
|
|
|
110
128
|
queue.add(async () => {
|
|
111
|
-
const signals = [signal]
|
|
112
|
-
|
|
113
|
-
if (queryFuncTimeout != null) {
|
|
114
|
-
signals.push(AbortSignal.timeout(queryFuncTimeout))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const compoundSignal = anySignal(signals)
|
|
118
|
-
|
|
119
|
-
// this signal can get listened to a lot
|
|
120
|
-
setMaxListeners(Infinity, compoundSignal)
|
|
121
|
-
|
|
122
129
|
try {
|
|
123
130
|
for await (const event of query({
|
|
124
131
|
...options,
|
|
125
132
|
key,
|
|
126
133
|
peer,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
path: {
|
|
135
|
+
index: path,
|
|
136
|
+
queued: queue.queued,
|
|
137
|
+
running: queue.running,
|
|
138
|
+
total: queue.size
|
|
139
|
+
},
|
|
140
|
+
numPaths,
|
|
141
|
+
peerKadId,
|
|
142
|
+
signal
|
|
130
143
|
})) {
|
|
131
|
-
if (compoundSignal.aborted) {
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
144
|
// if there are closer peers and the query has not completed, continue the query
|
|
136
145
|
if (event.name === 'PEER_RESPONSE') {
|
|
137
146
|
for (const closerPeer of event.closer) {
|
|
138
|
-
if (peersSeen.has(closerPeer.id)) { // eslint-disable-line max-depth
|
|
139
|
-
log
|
|
147
|
+
if (peersSeen.has(closerPeer.id.toMultihash().bytes)) { // eslint-disable-line max-depth
|
|
148
|
+
log('already seen %p in query', closerPeer.id)
|
|
140
149
|
continue
|
|
141
150
|
}
|
|
142
151
|
|
|
@@ -155,51 +164,52 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
|
|
|
155
164
|
|
|
156
165
|
// only continue query if closer peer is actually closer
|
|
157
166
|
if (uint8ArrayXorCompare(closerPeerXor, peerXor) !== -1) { // eslint-disable-line max-depth
|
|
158
|
-
log
|
|
167
|
+
log('skipping %p as they are not closer to %b than %p', closerPeer.id, key, peer)
|
|
159
168
|
continue
|
|
160
169
|
}
|
|
161
170
|
|
|
162
|
-
log
|
|
163
|
-
queryPeer(closerPeer
|
|
171
|
+
log('querying closer peer %p', closerPeer.id)
|
|
172
|
+
queryPeer(closerPeer, closerPeerKadId)
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
events.push({
|
|
177
|
+
...event,
|
|
178
|
+
path: {
|
|
179
|
+
index: path,
|
|
180
|
+
queued: queue.queued,
|
|
181
|
+
running: queue.running,
|
|
182
|
+
total: queue.size
|
|
183
|
+
}
|
|
169
184
|
})
|
|
170
185
|
}
|
|
171
186
|
} catch (err: any) {
|
|
172
|
-
if
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
// yield error event if query is continuing
|
|
188
|
+
events.push(queryErrorEvent({
|
|
189
|
+
from: peer.id,
|
|
190
|
+
error: err,
|
|
191
|
+
path: {
|
|
192
|
+
index: path,
|
|
193
|
+
queued: queue.queued,
|
|
194
|
+
running: queue.running - 1,
|
|
195
|
+
total: queue.size - 1
|
|
196
|
+
}
|
|
197
|
+
}, options))
|
|
180
198
|
}
|
|
181
199
|
}, {
|
|
182
200
|
distance: peerXor
|
|
183
201
|
}).catch(err => {
|
|
184
|
-
log.error(err)
|
|
202
|
+
log.error('error during query - %e', err)
|
|
185
203
|
})
|
|
186
204
|
}
|
|
187
205
|
|
|
188
|
-
// begin the query with the starting
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (event != null) {
|
|
195
|
-
yield event
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} catch (err) {
|
|
199
|
-
if (signal.aborted) {
|
|
200
|
-
throw new QueryAbortedError('Query aborted')
|
|
201
|
-
}
|
|
206
|
+
// begin the query with the starting peers
|
|
207
|
+
await Promise.all(
|
|
208
|
+
startingPeers.map(async startingPeer => {
|
|
209
|
+
queryPeer({ id: startingPeer, multiaddrs: [] }, await convertPeerId(startingPeer))
|
|
210
|
+
})
|
|
211
|
+
)
|
|
202
212
|
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
// yield results as they come in
|
|
214
|
+
yield * events
|
|
205
215
|
}
|
package/src/query/types.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type { QueryEvent } from '../index.js'
|
|
2
|
-
import type {
|
|
1
|
+
import type { DisjointPath, QueryEvent } from '../index.js'
|
|
2
|
+
import type { PeerInfo } from '@libp2p/interface'
|
|
3
3
|
|
|
4
4
|
export interface QueryContext {
|
|
5
5
|
// the key we are looking up
|
|
6
6
|
key: Uint8Array
|
|
7
7
|
// the current peer being queried
|
|
8
|
-
peer:
|
|
8
|
+
peer: PeerInfo
|
|
9
|
+
// the KAD ID of the peer being queried
|
|
10
|
+
peerKadId: Uint8Array
|
|
9
11
|
// if this signal emits an 'abort' event, any long-lived processes or requests started as part of this query should be terminated
|
|
10
|
-
signal
|
|
12
|
+
signal?: AbortSignal
|
|
11
13
|
// which disjoint path we are following
|
|
12
|
-
|
|
14
|
+
path: DisjointPath
|
|
13
15
|
// the total number of disjoint paths being executed
|
|
14
16
|
numPaths: number
|
|
15
17
|
}
|
package/src/query-self.ts
CHANGED
|
@@ -4,18 +4,16 @@ import length from 'it-length'
|
|
|
4
4
|
import { pipe } from 'it-pipe'
|
|
5
5
|
import take from 'it-take'
|
|
6
6
|
import pDefer from 'p-defer'
|
|
7
|
-
import { pEvent } from 'p-event'
|
|
8
7
|
import { QUERY_SELF_INTERVAL, QUERY_SELF_TIMEOUT, K, QUERY_SELF_INITIAL_INTERVAL } from './constants.js'
|
|
9
8
|
import { timeOperationMethod } from './utils.js'
|
|
10
9
|
import type { OperationMetrics } from './kad-dht.js'
|
|
11
10
|
import type { PeerRouting } from './peer-routing/index.js'
|
|
12
|
-
import type { RoutingTable } from './routing-table/index.js'
|
|
13
11
|
import type { ComponentLogger, Logger, Metrics, PeerId, Startable } from '@libp2p/interface'
|
|
14
12
|
import type { DeferredPromise } from 'p-defer'
|
|
13
|
+
|
|
15
14
|
export interface QuerySelfInit {
|
|
16
15
|
logPrefix: string
|
|
17
16
|
peerRouting: PeerRouting
|
|
18
|
-
routingTable: RoutingTable
|
|
19
17
|
count?: number
|
|
20
18
|
interval?: number
|
|
21
19
|
initialInterval?: number
|
|
@@ -28,6 +26,7 @@ export interface QuerySelfComponents {
|
|
|
28
26
|
peerId: PeerId
|
|
29
27
|
logger: ComponentLogger
|
|
30
28
|
metrics?: Metrics
|
|
29
|
+
events: EventTarget
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -37,7 +36,7 @@ export class QuerySelf implements Startable {
|
|
|
37
36
|
private readonly log: Logger
|
|
38
37
|
private readonly peerId: PeerId
|
|
39
38
|
private readonly peerRouting: PeerRouting
|
|
40
|
-
private readonly
|
|
39
|
+
private readonly events: EventTarget
|
|
41
40
|
private readonly count: number
|
|
42
41
|
private readonly interval: number
|
|
43
42
|
private readonly initialInterval: number
|
|
@@ -51,9 +50,9 @@ export class QuerySelf implements Startable {
|
|
|
51
50
|
constructor (components: QuerySelfComponents, init: QuerySelfInit) {
|
|
52
51
|
this.peerId = components.peerId
|
|
53
52
|
this.log = components.logger.forComponent(`${init.logPrefix}:query-self`)
|
|
53
|
+
this.events = components.events
|
|
54
54
|
this.running = false
|
|
55
55
|
this.peerRouting = init.peerRouting
|
|
56
|
-
this.routingTable = init.routingTable
|
|
57
56
|
this.count = init.count ?? K
|
|
58
57
|
this.interval = init.interval ?? QUERY_SELF_INTERVAL
|
|
59
58
|
this.initialInterval = init.initialInterval ?? QUERY_SELF_INITIAL_INTERVAL
|
|
@@ -122,20 +121,10 @@ export class QuerySelf implements Startable {
|
|
|
122
121
|
setMaxListeners(Infinity, signal, this.controller.signal)
|
|
123
122
|
|
|
124
123
|
try {
|
|
125
|
-
if (this.routingTable.size === 0) {
|
|
126
|
-
this.log('routing table was empty, waiting for some peers before running query')
|
|
127
|
-
// wait to discover at least one DHT peer that isn't us
|
|
128
|
-
await pEvent(this.routingTable, 'peer:add', {
|
|
129
|
-
signal,
|
|
130
|
-
filter: (event) => !this.peerId.equals(event.detail)
|
|
131
|
-
})
|
|
132
|
-
this.log('routing table has peers, continuing with query')
|
|
133
|
-
}
|
|
134
|
-
|
|
135
124
|
this.log('run self-query, look for %d peers timing out after %dms', this.count, this.queryTimeout)
|
|
136
125
|
const start = Date.now()
|
|
137
126
|
|
|
138
|
-
const
|
|
127
|
+
const peers = await pipe(
|
|
139
128
|
this.peerRouting.getClosestPeers(this.peerId.toMultihash().bytes, {
|
|
140
129
|
signal,
|
|
141
130
|
isSelfQuery: true
|
|
@@ -144,7 +133,16 @@ export class QuerySelf implements Startable {
|
|
|
144
133
|
async (source) => length(source)
|
|
145
134
|
)
|
|
146
135
|
|
|
147
|
-
|
|
136
|
+
const duration = Date.now() - start
|
|
137
|
+
|
|
138
|
+
this.log('self-query found %d peers in %dms', peers, duration)
|
|
139
|
+
|
|
140
|
+
this.events.dispatchEvent(new CustomEvent('kad-dht:query:self', {
|
|
141
|
+
detail: {
|
|
142
|
+
peers,
|
|
143
|
+
duration
|
|
144
|
+
}
|
|
145
|
+
}))
|
|
148
146
|
} catch (err: any) {
|
|
149
147
|
this.log.error('self-query error', err)
|
|
150
148
|
} finally {
|
|
@@ -91,7 +91,7 @@ export class ClosestPeers implements Startable {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
async updatePeerTags (): Promise<void> {
|
|
94
|
-
const newClosest = new PeerSet(this.newPeers?.peers.map(peer => peer.id))
|
|
94
|
+
const newClosest = new PeerSet(this.newPeers?.peers.map(({ peer }) => peer.id))
|
|
95
95
|
const added = newClosest.difference(this.closestPeers)
|
|
96
96
|
const removed = this.closestPeers.difference(newClosest)
|
|
97
97
|
this.closestPeers = newClosest
|
package/src/utils.ts
CHANGED
|
@@ -140,6 +140,15 @@ export function fromPublicKeyKey (key: Uint8Array): PeerId {
|
|
|
140
140
|
return peerIdFromMultihash(multihash)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
export function uint8ArrayToBigInt (buf: Uint8Array): bigint {
|
|
144
|
+
return BigInt(
|
|
145
|
+
`0x${
|
|
146
|
+
Array.from(buf)
|
|
147
|
+
.map(val => val.toString(16).padStart(2, '0')).join('')
|
|
148
|
+
}`
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
143
152
|
/**
|
|
144
153
|
* Create a new put record, encodes and signs it if enabled
|
|
145
154
|
*/
|