@ar.io/sdk 3.11.0-alpha.8 → 3.11.0-beta.1
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/README.md +52 -0
- package/bundles/web.bundle.min.js +106 -106
- package/lib/cjs/cli/cli.js +137 -122
- package/lib/cjs/cli/commands/readCommands.js +6 -0
- package/lib/cjs/cli/utils.js +4 -1
- package/lib/cjs/common/ant.js +5 -5
- package/lib/cjs/common/contracts/ao-process.js +2 -1
- package/lib/cjs/common/io.js +37 -0
- package/lib/cjs/common/wayfinder/{gateways.js → gateways/network.js} +3 -41
- package/lib/cjs/common/wayfinder/gateways/simple-cache.js +35 -0
- package/lib/cjs/common/wayfinder/gateways/static.js +13 -0
- package/lib/cjs/common/wayfinder/index.js +11 -8
- package/lib/cjs/common/wayfinder/routing/strategies/ping.js +72 -0
- package/lib/cjs/common/wayfinder/routing/strategies/ping.test.js +156 -0
- package/lib/cjs/common/wayfinder/routing/strategies/random.js +13 -0
- package/lib/cjs/common/wayfinder/routing/strategies/random.test.js +68 -0
- package/lib/cjs/common/wayfinder/routing/strategies/round-robin.js +42 -0
- package/lib/cjs/common/wayfinder/routing/strategies/round-robin.test.js +78 -0
- package/lib/cjs/common/wayfinder/routing/strategies/static.js +29 -0
- package/lib/cjs/common/wayfinder/routing/strategies/static.test.js +40 -0
- package/lib/cjs/common/wayfinder/verification/{data-root-verifier.js → strategies/data-root-verifier.js} +4 -4
- package/lib/cjs/common/wayfinder/verification/{hash-verifier.js → strategies/hash-verifier.js} +4 -4
- package/lib/cjs/common/wayfinder/{gateways/trusted-gateways.js → verification/trusted.js} +1 -1
- package/lib/cjs/common/wayfinder/wayfinder.js +397 -257
- package/lib/cjs/common/wayfinder/wayfinder.test.js +227 -208
- package/lib/cjs/version.js +1 -1
- package/lib/esm/cli/cli.js +138 -123
- package/lib/esm/cli/commands/readCommands.js +5 -0
- package/lib/esm/cli/utils.js +4 -1
- package/lib/esm/common/ant.js +5 -5
- package/lib/esm/common/contracts/ao-process.js +2 -1
- package/lib/esm/common/io.js +37 -0
- package/lib/esm/common/wayfinder/{gateways.js → gateways/network.js} +2 -38
- package/lib/esm/common/wayfinder/gateways/simple-cache.js +31 -0
- package/lib/esm/common/wayfinder/gateways/static.js +9 -0
- package/lib/esm/common/wayfinder/index.js +11 -8
- package/lib/esm/common/wayfinder/routing/strategies/ping.js +68 -0
- package/lib/esm/common/wayfinder/routing/strategies/ping.test.js +151 -0
- package/lib/esm/common/wayfinder/routing/strategies/random.js +9 -0
- package/lib/esm/common/wayfinder/routing/strategies/random.test.js +63 -0
- package/lib/esm/common/wayfinder/routing/strategies/round-robin.js +38 -0
- package/lib/esm/common/wayfinder/routing/strategies/round-robin.test.js +73 -0
- package/lib/esm/common/wayfinder/routing/strategies/static.js +25 -0
- package/lib/esm/common/wayfinder/routing/strategies/static.test.js +35 -0
- package/lib/esm/common/wayfinder/verification/{data-root-verifier.js → strategies/data-root-verifier.js} +2 -2
- package/lib/esm/common/wayfinder/verification/{hash-verifier.js → strategies/hash-verifier.js} +2 -2
- package/lib/esm/common/wayfinder/{gateways/trusted-gateways.js → verification/trusted.js} +1 -1
- package/lib/esm/common/wayfinder/wayfinder.js +395 -255
- package/lib/esm/common/wayfinder/wayfinder.test.js +227 -208
- package/lib/esm/version.js +1 -1
- package/lib/types/cli/commands/readCommands.d.ts +1 -0
- package/lib/types/common/io.d.ts +5 -2
- package/lib/types/common/wayfinder/{gateways.d.ts → gateways/network.d.ts} +3 -23
- package/lib/types/common/wayfinder/{routers/random.d.ts → gateways/simple-cache.d.ts} +12 -8
- package/lib/types/common/wayfinder/{routers → gateways}/static.d.ts +6 -7
- package/lib/types/common/wayfinder/index.d.ts +10 -7
- package/lib/types/common/wayfinder/{routers/simple-cache.d.ts → routing/strategies/ping.d.ts} +10 -11
- package/lib/types/common/wayfinder/routing/strategies/random.d.ts +21 -0
- package/lib/types/common/wayfinder/routing/strategies/round-robin.d.ts +29 -0
- package/lib/types/common/wayfinder/routing/strategies/static.d.ts +29 -0
- package/lib/types/common/wayfinder/verification/{data-root-verifier.d.ts → strategies/data-root-verifier.d.ts} +2 -2
- package/lib/types/common/wayfinder/verification/{hash-verifier.d.ts → strategies/hash-verifier.d.ts} +2 -2
- package/lib/types/common/wayfinder/{gateways/trusted-gateways.d.ts → verification/trusted.d.ts} +1 -1
- package/lib/types/common/wayfinder/wayfinder.d.ts +111 -77
- package/lib/types/types/io.d.ts +16 -1
- package/lib/types/types/wayfinder.d.ts +8 -4
- package/lib/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/lib/cjs/common/wayfinder/routers/priority.js +0 -29
- package/lib/cjs/common/wayfinder/routers/priority.test.js +0 -155
- package/lib/cjs/common/wayfinder/routers/random.js +0 -23
- package/lib/cjs/common/wayfinder/routers/random.test.js +0 -25
- package/lib/cjs/common/wayfinder/routers/simple-cache.js +0 -25
- package/lib/cjs/common/wayfinder/routers/simple-cache.test.js +0 -41
- package/lib/cjs/common/wayfinder/routers/static.js +0 -14
- package/lib/cjs/common/wayfinder/routers/static.test.js +0 -14
- package/lib/esm/common/wayfinder/routers/priority.js +0 -25
- package/lib/esm/common/wayfinder/routers/priority.test.js +0 -153
- package/lib/esm/common/wayfinder/routers/random.js +0 -19
- package/lib/esm/common/wayfinder/routers/random.test.js +0 -23
- package/lib/esm/common/wayfinder/routers/simple-cache.js +0 -21
- package/lib/esm/common/wayfinder/routers/simple-cache.test.js +0 -39
- package/lib/esm/common/wayfinder/routers/static.js +0 -10
- package/lib/esm/common/wayfinder/routers/static.test.js +0 -12
- package/lib/types/common/wayfinder/routers/priority.d.ts +0 -29
- /package/lib/types/common/wayfinder/{routers/priority.test.d.ts → routing/strategies/ping.test.d.ts} +0 -0
- /package/lib/types/common/wayfinder/{routers → routing/strategies}/random.test.d.ts +0 -0
- /package/lib/types/common/wayfinder/{routers/simple-cache.test.d.ts → routing/strategies/round-robin.test.d.ts} +0 -0
- /package/lib/types/common/wayfinder/{routers → routing/strategies}/static.test.d.ts +0 -0
|
@@ -25,50 +25,47 @@ const node_events_1 = __importDefault(require("node:events"));
|
|
|
25
25
|
const node_stream_1 = require("node:stream");
|
|
26
26
|
const io_js_1 = require("../io.js");
|
|
27
27
|
const logger_js_1 = require("../logger.js");
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
28
|
+
const network_js_1 = require("./gateways/network.js");
|
|
29
|
+
const simple_cache_js_1 = require("./gateways/simple-cache.js");
|
|
30
|
+
const static_js_1 = require("./gateways/static.js");
|
|
31
|
+
const ping_js_1 = require("./routing/strategies/ping.js");
|
|
32
|
+
const hash_verifier_js_1 = require("./verification/strategies/hash-verifier.js");
|
|
33
|
+
const trusted_js_1 = require("./verification/trusted.js");
|
|
32
34
|
// known regexes for wayfinder urls
|
|
33
35
|
exports.arnsRegex = /^[a-z0-9_-]{1,51}$/;
|
|
34
36
|
exports.txIdRegex = /^[A-Za-z0-9_-]{43}$/;
|
|
35
37
|
/**
|
|
36
|
-
* Core function
|
|
38
|
+
* Core function that converts a wayfinder url to the proper ar-io gateway URL
|
|
37
39
|
* @param originalUrl - the wayfinder url to resolve
|
|
38
|
-
* @param
|
|
40
|
+
* @param selectedGateway - the target gateway to resolve the url against
|
|
39
41
|
* @returns the resolved url that can be used to make a request
|
|
40
42
|
*/
|
|
41
|
-
const resolveWayfinderUrl =
|
|
43
|
+
const resolveWayfinderUrl = ({ originalUrl, selectedGateway, logger, }) => {
|
|
42
44
|
if (originalUrl.toString().startsWith('ar://')) {
|
|
43
45
|
logger?.debug(`Applying wayfinder routing protocol to ${originalUrl}`, {
|
|
44
46
|
originalUrl,
|
|
45
47
|
});
|
|
46
|
-
const targetGatewayUrl = new URL(await targetGateway());
|
|
47
|
-
logger?.debug(`Selected target gateway: ${targetGatewayUrl}`, {
|
|
48
|
-
originalUrl,
|
|
49
|
-
targetGateway: targetGatewayUrl,
|
|
50
|
-
});
|
|
51
48
|
const [, path] = originalUrl.toString().split('ar://');
|
|
52
49
|
// e.g. ar:///info should route to the info endpoint of the target gateway
|
|
53
50
|
if (path.startsWith('/')) {
|
|
54
|
-
logger?.debug(`Routing to ${path.slice(1)} on ${
|
|
51
|
+
logger?.debug(`Routing to ${path.slice(1)} on ${selectedGateway}`, {
|
|
55
52
|
originalUrl,
|
|
56
|
-
|
|
53
|
+
selectedGateway,
|
|
57
54
|
});
|
|
58
|
-
return new URL(path.slice(1),
|
|
55
|
+
return new URL(path.slice(1), selectedGateway);
|
|
59
56
|
}
|
|
60
57
|
// TODO: this breaks 43 character named arns names - we should check a a local name cache list before resolving raw transaction ids
|
|
61
58
|
if (exports.txIdRegex.test(path)) {
|
|
62
59
|
const [txId, ...rest] = path.split('/');
|
|
63
|
-
return new URL(`${txId}${rest.join('/')}`,
|
|
60
|
+
return new URL(`${txId}${rest.join('/')}`, selectedGateway);
|
|
64
61
|
}
|
|
65
62
|
if (exports.arnsRegex.test(path)) {
|
|
66
63
|
// TODO: tests to ensure arns names support query params and paths
|
|
67
64
|
const [name, ...rest] = path.split('/');
|
|
68
|
-
const arnsUrl = `${
|
|
65
|
+
const arnsUrl = `${selectedGateway.protocol}//${name}.${selectedGateway.hostname}${selectedGateway.port ? `:${selectedGateway.port}` : ''}`;
|
|
69
66
|
logger?.debug(`Routing to ${path} on ${arnsUrl}`, {
|
|
70
67
|
originalUrl,
|
|
71
|
-
|
|
68
|
+
selectedGateway,
|
|
72
69
|
});
|
|
73
70
|
return new URL(rest.join('/'), arnsUrl);
|
|
74
71
|
}
|
|
@@ -88,7 +85,7 @@ class WayfinderEmitter extends node_events_1.default {
|
|
|
88
85
|
} = {}) {
|
|
89
86
|
super();
|
|
90
87
|
if (onVerificationPassed) {
|
|
91
|
-
this.on('verification-
|
|
88
|
+
this.on('verification-succeeded', onVerificationPassed);
|
|
92
89
|
}
|
|
93
90
|
if (onVerificationFailed) {
|
|
94
91
|
this.on('verification-failed', onVerificationFailed);
|
|
@@ -105,7 +102,7 @@ class WayfinderEmitter extends node_events_1.default {
|
|
|
105
102
|
}
|
|
106
103
|
}
|
|
107
104
|
exports.WayfinderEmitter = WayfinderEmitter;
|
|
108
|
-
function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, emitter, }) {
|
|
105
|
+
function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, emitter, strict = false, }) {
|
|
109
106
|
// taps node streams
|
|
110
107
|
if (originalStream instanceof node_stream_1.Readable &&
|
|
111
108
|
typeof originalStream.pipe === 'function') {
|
|
@@ -133,26 +130,39 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
133
130
|
});
|
|
134
131
|
originalStream.on('end', async () => {
|
|
135
132
|
streamToVerify.end(); // triggers verifier completion and completes the verification promise
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
133
|
+
if (strict) {
|
|
134
|
+
// in strict mode, we wait for verification to complete before ending the client stream
|
|
135
|
+
try {
|
|
136
|
+
await verificationPromise;
|
|
137
|
+
emitter?.emit('verification-succeeded', { txId });
|
|
138
|
+
tappedClientStream.end();
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
emitter?.emit('verification-failed', { error, txId });
|
|
142
|
+
// In strict mode, destroy the client stream with the error
|
|
143
|
+
tappedClientStream.destroy(new Error('Verification failed', { cause: error }));
|
|
144
|
+
}
|
|
142
145
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
else {
|
|
147
|
+
// in non-strict mode, we end the client stream immediately and handle verification asynchronously
|
|
148
|
+
tappedClientStream.end();
|
|
149
|
+
// trigger the verification promise and emit events for the result
|
|
150
|
+
verificationPromise
|
|
151
|
+
.then(() => {
|
|
152
|
+
emitter?.emit('verification-succeeded', { txId });
|
|
153
|
+
})
|
|
154
|
+
.catch((error) => {
|
|
155
|
+
emitter?.emit('verification-failed', { error, txId });
|
|
147
156
|
});
|
|
148
|
-
tappedClientStream.destroy(error);
|
|
149
157
|
}
|
|
150
158
|
});
|
|
151
159
|
originalStream.on('error', (err) => {
|
|
160
|
+
// emit the verification failed event
|
|
152
161
|
emitter?.emit('verification-failed', {
|
|
153
162
|
error: err,
|
|
154
163
|
txId,
|
|
155
164
|
});
|
|
165
|
+
// destroy both streams and propagate the original stream error
|
|
156
166
|
streamToVerify.destroy(err);
|
|
157
167
|
tappedClientStream.destroy(err);
|
|
158
168
|
});
|
|
@@ -174,20 +184,37 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
174
184
|
async pull(controller) {
|
|
175
185
|
const { done, value } = await reader.read();
|
|
176
186
|
if (done) {
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
txId
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
if (strict) {
|
|
188
|
+
// in strict mode, we wait for verification to complete before closing the controller
|
|
189
|
+
try {
|
|
190
|
+
await verificationPromise;
|
|
191
|
+
emitter?.emit('verification-succeeded', { txId });
|
|
192
|
+
controller.close();
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
emitter?.emit('verification-failed', {
|
|
196
|
+
txId,
|
|
197
|
+
error: err,
|
|
198
|
+
});
|
|
199
|
+
// In strict mode, we report the error to the client stream
|
|
200
|
+
controller.error(new Error('Verification failed', { cause: err }));
|
|
201
|
+
}
|
|
184
202
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
203
|
+
else {
|
|
204
|
+
// in non-strict mode, we close the controller immediately and handle verification asynchronously
|
|
205
|
+
controller.close();
|
|
206
|
+
// trigger the verification promise and emit events for the result
|
|
207
|
+
verificationPromise
|
|
208
|
+
.then(() => {
|
|
209
|
+
emitter?.emit('verification-succeeded', { txId });
|
|
210
|
+
})
|
|
211
|
+
.catch((err) => {
|
|
212
|
+
emitter?.emit('verification-failed', {
|
|
213
|
+
txId,
|
|
214
|
+
error: err,
|
|
215
|
+
});
|
|
216
|
+
// we don't call controller.error() to avoid breaking the client stream
|
|
189
217
|
});
|
|
190
|
-
controller.error(err);
|
|
191
218
|
}
|
|
192
219
|
}
|
|
193
220
|
else {
|
|
@@ -201,7 +228,9 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
201
228
|
}
|
|
202
229
|
},
|
|
203
230
|
cancel(reason) {
|
|
231
|
+
// cancel the reader regardless of verification status
|
|
204
232
|
reader.cancel(reason);
|
|
233
|
+
// emit the verification cancellation event
|
|
205
234
|
emitter?.emit('verification-failed', {
|
|
206
235
|
txId,
|
|
207
236
|
error: new Error('Verification cancelled', {
|
|
@@ -210,6 +239,8 @@ function tapAndVerifyStream({ originalStream, contentLength, verifyData, txId, e
|
|
|
210
239
|
},
|
|
211
240
|
}),
|
|
212
241
|
});
|
|
242
|
+
// note: we don't block or throw errors here even in strict mode
|
|
243
|
+
// since the stream is already being cancelled by the client
|
|
213
244
|
},
|
|
214
245
|
});
|
|
215
246
|
return clientStreamWithVerification;
|
|
@@ -244,7 +275,7 @@ function wrapVerifiedResponse(original, newBody, txId) {
|
|
|
244
275
|
* @param resolveUrl - the function to construct the redirect url for ar:// requests
|
|
245
276
|
* @returns a wrapped http client that supports ar:// protocol
|
|
246
277
|
*/
|
|
247
|
-
const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, emitter = new WayfinderEmitter(), logger, }) => {
|
|
278
|
+
const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, selectGateway, emitter = new WayfinderEmitter(), logger, strict = false, }) => {
|
|
248
279
|
const wayfinderRedirect = async (fn, rawArgs) => {
|
|
249
280
|
// TODO: handle if first arg is not a string (i.e. just return the result of the function call)
|
|
250
281
|
const [originalUrl, ...rest] = rawArgs;
|
|
@@ -252,157 +283,226 @@ const createWayfinderClient = ({ httpClient, resolveUrl, verifyData, emitter = n
|
|
|
252
283
|
logger?.debug('Original URL is not a string, skipping routing', {
|
|
253
284
|
originalUrl,
|
|
254
285
|
});
|
|
286
|
+
emitter?.emit('routing-skipped', {
|
|
287
|
+
originalUrl: JSON.stringify(originalUrl),
|
|
288
|
+
});
|
|
255
289
|
return fn(...rawArgs);
|
|
256
290
|
}
|
|
257
291
|
emitter?.emit('routing-started', {
|
|
258
292
|
originalUrl: originalUrl.toString(),
|
|
259
293
|
});
|
|
260
|
-
//
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
});
|
|
269
|
-
logger?.debug(`Redirecting request to ${redirectUrl}`, {
|
|
270
|
-
originalUrl,
|
|
271
|
-
redirectUrl,
|
|
272
|
-
});
|
|
273
|
-
// make the request to the target gateway using the redirect url and http client
|
|
274
|
-
const response = await fn(redirectUrl.toString(), ...rest);
|
|
275
|
-
// TODO: trigger a routing event with the raw response object?
|
|
276
|
-
logger?.debug(`Successfully routed request to ${redirectUrl}`, {
|
|
277
|
-
redirectUrl,
|
|
278
|
-
originalUrl,
|
|
279
|
-
});
|
|
280
|
-
// only verify data if the redirect url is different from the original url
|
|
281
|
-
if (response && redirectUrl.toString() !== originalUrl.toString()) {
|
|
282
|
-
if (verifyData) {
|
|
283
|
-
// if the headers do not have .get on them, we need to parse the headers manually
|
|
284
|
-
const headers = new Headers();
|
|
285
|
-
let headersObject = response.headers ?? {};
|
|
286
|
-
if (typeof headersObject.get !== 'function') {
|
|
287
|
-
headersObject = Object.fromEntries(headersObject);
|
|
288
|
-
for (const [key, value] of Object.entries(headersObject)) {
|
|
289
|
-
headers.set(key, value);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
else {
|
|
293
|
-
for (const [key, value] of headersObject.entries()) {
|
|
294
|
-
headers.set(key, value);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
// transaction id is either in the response headers or the path of the request as the first parameter
|
|
298
|
-
// TODO: we may want to move this parsing to be returned by the resolveUrl function depending on the redirect URL we've constructed
|
|
299
|
-
const txId = headers.get('x-arns-resolved-id') ??
|
|
300
|
-
redirectUrl.pathname.split('/')[1];
|
|
301
|
-
// TODO: validate nodes return content length for all responses
|
|
302
|
-
const contentLength = +(headers.get('content-length') ?? 0);
|
|
303
|
-
if (!exports.txIdRegex.test(txId)) {
|
|
304
|
-
// no transaction id found, skip verification
|
|
305
|
-
logger?.debug('No transaction id found, skipping verification', {
|
|
306
|
-
redirectUrl,
|
|
307
|
-
originalUrl,
|
|
308
|
-
});
|
|
309
|
-
emitter?.emit('verification-skipped', {
|
|
310
|
-
originalUrl,
|
|
311
|
-
});
|
|
312
|
-
return response;
|
|
313
|
-
}
|
|
314
|
-
emitter?.emit('identified-transaction-id', {
|
|
294
|
+
// TODO: by default we will retry 3 times but this should be configurable and moved to a routing strategy
|
|
295
|
+
const maxRetries = 3;
|
|
296
|
+
const retryDelay = 1000;
|
|
297
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
298
|
+
try {
|
|
299
|
+
// select the target gateway
|
|
300
|
+
const selectedGateway = await selectGateway();
|
|
301
|
+
logger?.debug('Selected gateway', {
|
|
315
302
|
originalUrl,
|
|
316
|
-
|
|
317
|
-
txId,
|
|
303
|
+
selectedGateway: selectedGateway.toString(),
|
|
318
304
|
});
|
|
319
|
-
//
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
305
|
+
// route the request to the target gateway
|
|
306
|
+
const redirectUrl = resolveUrl({
|
|
307
|
+
originalUrl,
|
|
308
|
+
selectedGateway,
|
|
309
|
+
logger,
|
|
310
|
+
});
|
|
311
|
+
emitter?.emit('routing-succeeded', {
|
|
312
|
+
originalUrl,
|
|
313
|
+
selectedGateway: selectedGateway.toString(),
|
|
314
|
+
redirectUrl: redirectUrl.toString(),
|
|
315
|
+
});
|
|
316
|
+
logger?.debug(`Redirecting request`, {
|
|
317
|
+
originalUrl,
|
|
318
|
+
redirectUrl: redirectUrl.toString(),
|
|
319
|
+
});
|
|
320
|
+
// make the request to the target gateway using the redirect url and http client
|
|
321
|
+
const response = await fn(redirectUrl.toString(), ...rest);
|
|
322
|
+
// TODO: trigger a routing event with the raw response object?
|
|
323
|
+
logger?.debug(`Successfully routed request to gateway`, {
|
|
324
|
+
redirectUrl: redirectUrl.toString(),
|
|
325
|
+
originalUrl: originalUrl.toString(),
|
|
326
|
+
});
|
|
327
|
+
// only verify data if the redirect url is different from the original url
|
|
328
|
+
if (response && redirectUrl.toString() !== originalUrl.toString()) {
|
|
329
|
+
if (verifyData) {
|
|
330
|
+
// if the headers do not have .get on them, we need to parse the headers manually
|
|
331
|
+
const headers = new Headers();
|
|
332
|
+
const headersObject = response.headers ?? {};
|
|
333
|
+
if (headersObject instanceof Map) {
|
|
334
|
+
for (const [key, value] of headersObject.entries()) {
|
|
335
|
+
headers.set(key, value);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (headersObject instanceof Headers) {
|
|
339
|
+
for (const [key, value] of headersObject.entries()) {
|
|
340
|
+
headers.set(key, value);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else if (headersObject !== undefined &&
|
|
344
|
+
typeof headersObject === 'object') {
|
|
345
|
+
for (const [key, value] of Object.entries(headersObject)) {
|
|
346
|
+
headers.set(key, value);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
throw new Error('Gateway did not return headers needed for verification', {
|
|
351
|
+
cause: {
|
|
352
|
+
redirectUrl: redirectUrl.toString(),
|
|
353
|
+
originalUrl: originalUrl.toString(),
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// transaction id is either in the response headers or the path of the request as the first parameter
|
|
358
|
+
// TODO: we may want to move this parsing to be returned by the resolveUrl function depending on the redirect URL we've constructed
|
|
359
|
+
const txId = headers.get('x-arns-resolved-id') ??
|
|
360
|
+
redirectUrl.pathname.split('/')[1];
|
|
361
|
+
// TODO: validate nodes return content length for all responses
|
|
362
|
+
const contentLength = +(headers.get('content-length') ?? 0);
|
|
363
|
+
if (!exports.txIdRegex.test(txId)) {
|
|
364
|
+
// no transaction id found, skip verification
|
|
365
|
+
logger?.debug('No transaction id found, skipping verification', {
|
|
366
|
+
redirectUrl: redirectUrl.toString(),
|
|
367
|
+
originalUrl,
|
|
368
|
+
});
|
|
369
|
+
emitter?.emit('verification-skipped', {
|
|
370
|
+
originalUrl,
|
|
371
|
+
});
|
|
372
|
+
return response;
|
|
373
|
+
}
|
|
374
|
+
emitter?.emit('identified-transaction-id', {
|
|
375
|
+
originalUrl,
|
|
376
|
+
selectedGateway: redirectUrl.toString(),
|
|
349
377
|
txId,
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
txId,
|
|
364
|
-
emitter,
|
|
365
|
-
});
|
|
366
|
-
if (responseBody instanceof ReadableStream) {
|
|
367
|
-
// specific to fetch
|
|
368
|
-
return wrapVerifiedResponse(response, newClientStream, txId);
|
|
369
|
-
}
|
|
370
|
-
else if (responseBody instanceof node_stream_1.Readable) {
|
|
371
|
-
// overwrite the response body with the new client stream
|
|
372
|
-
response.txId = txId;
|
|
373
|
-
response.body = newClientStream;
|
|
374
|
-
return response;
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
// TODO: content-application/json and it's smaller than 10mb
|
|
378
|
-
// TODO: add tests and verify this works for all non-Readable/streamed responses
|
|
379
|
-
try {
|
|
380
|
-
// if strict set to true
|
|
381
|
-
await verifyData({
|
|
382
|
-
data: responseBody,
|
|
383
|
-
txId,
|
|
378
|
+
});
|
|
379
|
+
// parse out the key that contains the response body, we'll use it later when updating the response object
|
|
380
|
+
const responseDataKey = response.body
|
|
381
|
+
? 'body'
|
|
382
|
+
: response.data
|
|
383
|
+
? 'data'
|
|
384
|
+
: undefined;
|
|
385
|
+
if (responseDataKey === undefined) {
|
|
386
|
+
throw new Error('No data body or data provided, skipping verification', {
|
|
387
|
+
cause: {
|
|
388
|
+
redirectUrl: redirectUrl.toString(),
|
|
389
|
+
originalUrl: originalUrl.toString(),
|
|
390
|
+
},
|
|
384
391
|
});
|
|
385
|
-
|
|
386
|
-
|
|
392
|
+
}
|
|
393
|
+
const responseBody = response[responseDataKey];
|
|
394
|
+
// TODO: determine if it is data item or L1 transaction, and tell the verifier accordingly, just drop in hit to graphql now
|
|
395
|
+
if (txId === undefined) {
|
|
396
|
+
throw new Error('Failed to parse data hash from response headers', {
|
|
397
|
+
cause: {
|
|
398
|
+
redirectUrl: redirectUrl.toString(),
|
|
399
|
+
originalUrl: originalUrl.toString(),
|
|
400
|
+
txId,
|
|
401
|
+
},
|
|
387
402
|
});
|
|
388
403
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
404
|
+
else if (responseBody === undefined) {
|
|
405
|
+
throw new Error('No data body provided, skipping verification', {
|
|
406
|
+
cause: {
|
|
407
|
+
redirectUrl: redirectUrl.toString(),
|
|
408
|
+
originalUrl: originalUrl.toString(),
|
|
409
|
+
txId,
|
|
410
|
+
},
|
|
393
411
|
});
|
|
394
|
-
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
logger?.debug('Verifying data hash for txId', {
|
|
415
|
+
redirectUrl: redirectUrl.toString(),
|
|
416
|
+
originalUrl: originalUrl.toString(),
|
|
395
417
|
txId,
|
|
396
|
-
error,
|
|
397
418
|
});
|
|
419
|
+
if (responseBody instanceof ReadableStream ||
|
|
420
|
+
responseBody instanceof node_stream_1.Readable) {
|
|
421
|
+
const newClientStream = tapAndVerifyStream({
|
|
422
|
+
originalStream: responseBody,
|
|
423
|
+
contentLength,
|
|
424
|
+
verifyData,
|
|
425
|
+
txId,
|
|
426
|
+
emitter,
|
|
427
|
+
strict,
|
|
428
|
+
});
|
|
429
|
+
if (response instanceof Response) {
|
|
430
|
+
// specific to fetch
|
|
431
|
+
return wrapVerifiedResponse(response, newClientStream, txId);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
// overwrite the response body with the new client stream
|
|
435
|
+
response.txId = txId;
|
|
436
|
+
response.body = newClientStream;
|
|
437
|
+
return response;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
// TODO: content-application/json and it's smaller than 10mb
|
|
442
|
+
// TODO: add tests and verify this works for all non-Readable/streamed responses
|
|
443
|
+
if (strict) {
|
|
444
|
+
// In strict mode, wait for verification before returning response
|
|
445
|
+
try {
|
|
446
|
+
await verifyData({
|
|
447
|
+
data: responseBody,
|
|
448
|
+
txId,
|
|
449
|
+
});
|
|
450
|
+
emitter?.emit('verification-succeeded', { txId });
|
|
451
|
+
return response;
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
logger?.debug('Failed to verify data hash', {
|
|
455
|
+
error,
|
|
456
|
+
txId,
|
|
457
|
+
});
|
|
458
|
+
emitter?.emit('verification-failed', { txId, error });
|
|
459
|
+
throw new Error('Verification failed', { cause: error });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
// In non-strict mode, perform verification in the background
|
|
464
|
+
verifyData({
|
|
465
|
+
data: responseBody,
|
|
466
|
+
txId,
|
|
467
|
+
})
|
|
468
|
+
.then(() => {
|
|
469
|
+
emitter?.emit('verification-succeeded', { txId });
|
|
470
|
+
})
|
|
471
|
+
.catch((error) => {
|
|
472
|
+
logger?.debug('Failed to verify data hash', {
|
|
473
|
+
error,
|
|
474
|
+
txId,
|
|
475
|
+
});
|
|
476
|
+
emitter?.emit('verification-failed', { txId, error });
|
|
477
|
+
});
|
|
478
|
+
return response;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
398
481
|
}
|
|
399
|
-
return response;
|
|
400
482
|
}
|
|
401
483
|
}
|
|
484
|
+
// TODO: if strict - wait for verification to finish and succeed before returning the response
|
|
485
|
+
return response;
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
logger?.debug('Failed to route request', {
|
|
489
|
+
error: error.message,
|
|
490
|
+
stack: error.stack,
|
|
491
|
+
originalUrl,
|
|
492
|
+
attempt: i + 1,
|
|
493
|
+
maxRetries,
|
|
494
|
+
});
|
|
495
|
+
if (i < maxRetries - 1) {
|
|
496
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
497
|
+
}
|
|
402
498
|
}
|
|
403
499
|
}
|
|
404
|
-
|
|
405
|
-
|
|
500
|
+
throw new Error('Failed to route request after max retries', {
|
|
501
|
+
cause: {
|
|
502
|
+
originalUrl,
|
|
503
|
+
maxRetries,
|
|
504
|
+
},
|
|
505
|
+
});
|
|
406
506
|
};
|
|
407
507
|
return new Proxy(httpClient, {
|
|
408
508
|
// support direct calls: fetch('ar://…', options)
|
|
@@ -427,87 +527,116 @@ exports.createWayfinderClient = createWayfinderClient;
|
|
|
427
527
|
*/
|
|
428
528
|
class Wayfinder {
|
|
429
529
|
/**
|
|
430
|
-
* The
|
|
530
|
+
* The native http client used by wayfinder. By default, the native fetch api is used.
|
|
431
531
|
*
|
|
432
532
|
* @example
|
|
433
533
|
* const wayfinder = new Wayfinder({
|
|
434
|
-
*
|
|
435
|
-
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
436
|
-
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
437
|
-
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
438
|
-
* }),
|
|
439
|
-
* }),
|
|
534
|
+
* httpClient: axios,
|
|
440
535
|
* });
|
|
441
536
|
*
|
|
442
|
-
* // Returns a target gateway based on the routing strategy
|
|
443
|
-
* const targetGateway = await wayfinder.router.getTargetGateway();
|
|
444
537
|
*/
|
|
445
|
-
|
|
538
|
+
httpClient;
|
|
446
539
|
/**
|
|
447
|
-
* The
|
|
540
|
+
* The gateways provider is responsible for providing the list of gateways to use for routing requests.
|
|
448
541
|
*
|
|
449
542
|
* @example
|
|
450
543
|
* const wayfinder = new Wayfinder({
|
|
451
|
-
*
|
|
452
|
-
* gatewaysProvider: new
|
|
453
|
-
*
|
|
454
|
-
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
455
|
-
* }),
|
|
544
|
+
* gatewaysProvider: new SimpleCacheGatewaysProvider({
|
|
545
|
+
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
546
|
+
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
456
547
|
* }),
|
|
457
|
-
* httpClient: axios,
|
|
458
548
|
* });
|
|
459
|
-
*
|
|
460
549
|
*/
|
|
461
|
-
|
|
550
|
+
gatewaysProvider;
|
|
462
551
|
/**
|
|
463
|
-
* The
|
|
552
|
+
* The routing strategy to use when routing requests.
|
|
464
553
|
*
|
|
465
554
|
* @example
|
|
466
555
|
* const wayfinder = new Wayfinder({
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
* gatewaysProvider: new NetworkGatewaysProvider({ ario: ARIO.mainnet() }),
|
|
470
|
-
* ttlSeconds: 60 * 60 * 24, // 1 day
|
|
471
|
-
* }),
|
|
556
|
+
* strategy: new FastestPingStrategy({
|
|
557
|
+
* timeoutMs: 1000,
|
|
472
558
|
* }),
|
|
473
|
-
* httpClient: axios,
|
|
474
559
|
* });
|
|
560
|
+
*/
|
|
561
|
+
routingStrategy;
|
|
562
|
+
/**
|
|
563
|
+
* A helper function that resolves the redirect url for ar:// requests to a target gateway.
|
|
564
|
+
*
|
|
565
|
+
* Note: no verification is done when resolving an ar://<path> url to a wayfinder route.
|
|
566
|
+
* In order to verify the data, you must use the `request` function or request the data and
|
|
567
|
+
* verify it yourself via the `verifyData` function.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* const { resolveUrl } = new Wayfinder();
|
|
475
571
|
*
|
|
476
572
|
* // returns the redirected URL based on the routing strategy and the original url
|
|
477
|
-
* const redirectUrl = await
|
|
573
|
+
* const redirectUrl = await resolveUrl({ originalUrl: 'ar://example' });
|
|
574
|
+
*
|
|
575
|
+
* window.open(redirectUrl.toString(), '_blank');
|
|
478
576
|
*/
|
|
479
577
|
resolveUrl;
|
|
480
578
|
/**
|
|
481
|
-
*
|
|
579
|
+
*
|
|
580
|
+
* A wrapped http client that supports ar:// protocol. If a verification strategy is provided,
|
|
581
|
+
* the request will be verified and events will be emitted as the request is processed.
|
|
482
582
|
*
|
|
483
583
|
* @example
|
|
484
|
-
* const
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* gatewaysProvider: new
|
|
488
|
-
*
|
|
584
|
+
* const wayfinder = new Wayfinder({
|
|
585
|
+
* verificationStrategy: new HashVerificationStrategy({
|
|
586
|
+
* trustedHashProvider: new TrustedGatewaysHashProvider({
|
|
587
|
+
* gatewaysProvider: new StaticGatewaysProvider({
|
|
588
|
+
* gateways: ['https://permagate.io'],
|
|
589
|
+
* }),
|
|
489
590
|
* }),
|
|
490
591
|
* }),
|
|
491
|
-
* httpClient: axios,
|
|
492
|
-
* });;
|
|
493
|
-
*
|
|
494
|
-
* const response = await wayfind('ar://example', {
|
|
495
|
-
* method: 'POST',
|
|
496
|
-
* data: {
|
|
497
|
-
* name: 'John Doe',
|
|
498
|
-
* },
|
|
499
592
|
* })
|
|
593
|
+
*
|
|
594
|
+
* // request an arns name
|
|
595
|
+
* const response = await wayfinder.request('ar://ardrive')
|
|
596
|
+
*
|
|
597
|
+
* // request a transaction id
|
|
598
|
+
* const response = await wayfinder.request('ar://1234567890')
|
|
599
|
+
*
|
|
600
|
+
* // request a transaction id with a custom http client
|
|
601
|
+
* const response = await wayfinder.request('ar://1234567890')
|
|
602
|
+
*
|
|
603
|
+
* // Set strict mode to true to make verification blocking
|
|
604
|
+
* const wayfinder = new Wayfinder({
|
|
605
|
+
* strict: true,
|
|
606
|
+
* });
|
|
607
|
+
*
|
|
608
|
+
* // This will throw an error if verification fails
|
|
609
|
+
* try {
|
|
610
|
+
* const response = await wayfinder.request('ar://1234567890');
|
|
611
|
+
* } catch (error) {
|
|
612
|
+
* console.error('Verification failed', error);
|
|
613
|
+
* }
|
|
500
614
|
*/
|
|
501
615
|
request;
|
|
502
|
-
|
|
503
|
-
|
|
616
|
+
/**
|
|
617
|
+
* The function that verifies the data hash for a given transaction id.
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* const wayfinder = new Wayfinder({
|
|
621
|
+
* verifyData: (data, txId) => {
|
|
622
|
+
* // some custom verification logic
|
|
623
|
+
* return true;
|
|
624
|
+
* },
|
|
625
|
+
* });
|
|
626
|
+
*/
|
|
504
627
|
verifyData;
|
|
628
|
+
/**
|
|
629
|
+
* Whether verification should be strict (blocking) or not.
|
|
630
|
+
* If true, verification failures will cause requests to fail.
|
|
631
|
+
* If false, verification will be performed asynchronously and failures will only emit events.
|
|
632
|
+
*/
|
|
633
|
+
strict;
|
|
505
634
|
/**
|
|
506
635
|
* The event emitter for wayfinder that emits verification events.
|
|
507
636
|
*
|
|
508
637
|
* const wayfinder = new Wayfinder()
|
|
509
638
|
*
|
|
510
|
-
* wayfinder.emitter.on('verification-
|
|
639
|
+
* wayfinder.emitter.on('verification-succeeded', (event) => {
|
|
511
640
|
* console.log('Verification passed!', event);
|
|
512
641
|
* })
|
|
513
642
|
*
|
|
@@ -533,64 +662,75 @@ class Wayfinder {
|
|
|
533
662
|
*
|
|
534
663
|
* const response = await wayfind('ar://example');
|
|
535
664
|
*/
|
|
536
|
-
// TODO: consider changing this to events or event emitter
|
|
537
665
|
emitter;
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
666
|
+
/**
|
|
667
|
+
* The constructor for the wayfinder
|
|
668
|
+
* @param httpClient - the http client to use for requests
|
|
669
|
+
* @param routingStrategy - the routing strategy to use for requests
|
|
670
|
+
* @param verificationStrategy - the verification strategy to use for requests
|
|
671
|
+
* @param gatewaysProvider - the gateways provider to use for routing requests
|
|
672
|
+
* @param logger - the logger to use for logging
|
|
673
|
+
* @param strict - if true, verification will be blocking and will fail requests if verification fails; if false, verification will be non-blocking
|
|
674
|
+
*/
|
|
675
|
+
constructor({ httpClient = fetch, logger = logger_js_1.Logger.default, gatewaysProvider = new simple_cache_js_1.SimpleCacheGatewaysProvider({
|
|
676
|
+
gatewaysProvider: new network_js_1.NetworkGatewaysProvider({
|
|
677
|
+
ario: io_js_1.ARIO.mainnet(),
|
|
544
678
|
}),
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
679
|
+
ttlSeconds: 60 * 60, // 1 hour
|
|
680
|
+
}), routingStrategy = new ping_js_1.FastestPingRoutingStrategy({
|
|
681
|
+
timeoutMs: 1000,
|
|
682
|
+
}), verificationStrategy = new hash_verifier_js_1.HashVerificationStrategy({
|
|
683
|
+
trustedHashProvider: new trusted_js_1.TrustedGatewaysHashProvider({
|
|
684
|
+
gatewaysProvider: new static_js_1.StaticGatewaysProvider({
|
|
550
685
|
gateways: ['https://permagate.io'],
|
|
551
686
|
}),
|
|
552
687
|
}),
|
|
553
|
-
}), events
|
|
688
|
+
}), events = {
|
|
689
|
+
onVerificationPassed: (event) => {
|
|
690
|
+
logger.debug('Verification passed!', event);
|
|
691
|
+
},
|
|
692
|
+
onVerificationFailed: (event) => {
|
|
693
|
+
logger.error('Verification failed!', event);
|
|
694
|
+
},
|
|
695
|
+
onVerificationProgress: (event) => {
|
|
696
|
+
logger.debug('Verification progress!', event);
|
|
697
|
+
},
|
|
698
|
+
}, strict = false,
|
|
554
699
|
// TODO: stats provider
|
|
555
700
|
}) {
|
|
556
|
-
this.
|
|
701
|
+
this.routingStrategy = routingStrategy;
|
|
702
|
+
this.gatewaysProvider = gatewaysProvider;
|
|
557
703
|
this.httpClient = httpClient;
|
|
558
704
|
this.emitter = new WayfinderEmitter(events);
|
|
559
|
-
this.verifyData =
|
|
705
|
+
this.verifyData =
|
|
706
|
+
verificationStrategy.verifyData.bind(verificationStrategy);
|
|
707
|
+
this.strict = strict;
|
|
708
|
+
// top level function to easily resolve wayfinder urls using the routing strategy and gateways provider
|
|
560
709
|
this.resolveUrl = async ({ originalUrl, logger }) => {
|
|
710
|
+
const selectedGateway = await this.routingStrategy.selectGateway({
|
|
711
|
+
gateways: await this.gatewaysProvider.getGateways(),
|
|
712
|
+
});
|
|
561
713
|
return (0, exports.resolveWayfinderUrl)({
|
|
562
714
|
originalUrl,
|
|
563
|
-
|
|
715
|
+
selectedGateway,
|
|
564
716
|
logger,
|
|
565
717
|
});
|
|
566
718
|
};
|
|
719
|
+
// create a wayfinder client with the routing strategy and gateways provider
|
|
567
720
|
this.request = (0, exports.createWayfinderClient)({
|
|
568
721
|
httpClient,
|
|
569
|
-
|
|
722
|
+
selectGateway: async () => {
|
|
723
|
+
return this.routingStrategy.selectGateway({
|
|
724
|
+
gateways: await this.gatewaysProvider.getGateways(),
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
resolveUrl: exports.resolveWayfinderUrl,
|
|
570
728
|
verifyData: this.verifyData,
|
|
571
729
|
emitter: this.emitter,
|
|
572
730
|
logger,
|
|
731
|
+
strict,
|
|
573
732
|
});
|
|
574
|
-
logger?.debug(`Wayfinder initialized with ${
|
|
733
|
+
logger?.debug(`Wayfinder initialized with ${routingStrategy.constructor.name} routing strategy`);
|
|
575
734
|
}
|
|
576
735
|
}
|
|
577
736
|
exports.Wayfinder = Wayfinder;
|
|
578
|
-
// TODO: add a chart for verification strategies and what they do
|
|
579
|
-
// include complexity, performance, and security
|
|
580
|
-
// explain use cases that each strategy is best for
|
|
581
|
-
// e.g.
|
|
582
|
-
/**
|
|
583
|
-
*
|
|
584
|
-
* type | complexity | performance | security
|
|
585
|
-
* ---------|------------|-------------|---------
|
|
586
|
-
* hash | low | high | low
|
|
587
|
-
* ---------|------------|-------------|---------
|
|
588
|
-
* data root | medium | medium | low | only L1
|
|
589
|
-
* ---------|------------|-------------|---------
|
|
590
|
-
* signature | medium | medium | medium
|
|
591
|
-
* ---------|------------|-------------|---------
|
|
592
|
-
* composite | high | low | high
|
|
593
|
-
* ---------|------------|-------------|---------
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*/
|