@ar.io/sdk 3.12.2 → 3.13.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 +28 -1
- package/bundles/web.bundle.min.js +142 -147
- package/lib/cjs/cli/commands/arnsPurchaseCommands.js +10 -0
- package/lib/cjs/cli/options.js +5 -0
- package/lib/cjs/cli/utils.js +4 -0
- package/lib/cjs/common/index.js +2 -0
- package/lib/cjs/common/io.js +4 -0
- package/lib/cjs/common/turbo.js +2 -1
- package/lib/cjs/common/wayfinder/gateways/network.js +48 -0
- 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 +48 -0
- package/lib/cjs/common/wayfinder/routing/strategies/ping.js +72 -0
- package/lib/cjs/common/wayfinder/routing/strategies/preferred-with-fallback.js +50 -0
- package/lib/cjs/common/wayfinder/routing/strategies/random.js +13 -0
- package/lib/cjs/common/wayfinder/routing/strategies/round-robin.js +42 -0
- package/lib/cjs/common/wayfinder/routing/strategies/static.js +29 -0
- package/lib/cjs/common/wayfinder/verification/strategies/data-root-verifier.js +110 -0
- package/lib/cjs/common/wayfinder/verification/strategies/hash-verifier.js +27 -0
- package/lib/cjs/common/wayfinder/verification/trusted.js +125 -0
- package/lib/cjs/common/wayfinder/wayfinder.js +508 -0
- package/lib/cjs/types/wayfinder.js +3 -0
- package/lib/cjs/utils/hash.js +83 -31
- package/lib/cjs/version.js +1 -1
- package/lib/esm/cli/commands/arnsPurchaseCommands.js +11 -1
- package/lib/esm/cli/options.js +5 -0
- package/lib/esm/cli/utils.js +3 -0
- package/lib/esm/common/index.js +2 -0
- package/lib/esm/common/io.js +4 -0
- package/lib/esm/common/turbo.js +2 -1
- package/lib/esm/common/wayfinder/gateways/network.js +44 -0
- 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 +32 -0
- package/lib/esm/common/wayfinder/routing/strategies/ping.js +68 -0
- package/lib/esm/common/wayfinder/routing/strategies/preferred-with-fallback.js +46 -0
- package/lib/esm/common/wayfinder/routing/strategies/random.js +9 -0
- package/lib/esm/common/wayfinder/routing/strategies/round-robin.js +38 -0
- package/lib/esm/common/wayfinder/routing/strategies/static.js +25 -0
- package/lib/esm/common/wayfinder/verification/strategies/data-root-verifier.js +102 -0
- package/lib/esm/common/wayfinder/verification/strategies/hash-verifier.js +23 -0
- package/lib/esm/common/wayfinder/verification/trusted.js +121 -0
- package/lib/esm/common/wayfinder/wayfinder.js +499 -0
- package/lib/esm/types/wayfinder.js +2 -0
- package/lib/esm/utils/hash.js +74 -27
- package/lib/esm/version.js +1 -1
- package/lib/types/cli/options.d.ts +4 -0
- package/lib/types/cli/utils.d.ts +3 -0
- package/lib/types/common/index.d.ts +1 -0
- package/lib/types/common/turbo.d.ts +2 -1
- package/lib/types/common/wayfinder/gateways/network.d.ts +33 -0
- package/lib/types/common/wayfinder/gateways/simple-cache.d.ts +31 -0
- package/lib/types/common/wayfinder/gateways/static.d.ts +23 -0
- package/lib/types/common/wayfinder/index.d.ts +27 -0
- package/lib/types/common/wayfinder/routing/strategies/ping.d.ts +27 -0
- package/lib/types/common/wayfinder/routing/strategies/preferred-with-fallback.d.ts +31 -0
- 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/strategies/data-root-verifier.d.ts +27 -0
- package/lib/types/common/wayfinder/verification/strategies/hash-verifier.d.ts +26 -0
- package/lib/types/common/wayfinder/verification/trusted.d.ts +51 -0
- package/lib/types/common/wayfinder/wayfinder.d.ts +257 -0
- package/lib/types/types/io.d.ts +4 -0
- package/lib/types/types/wayfinder.d.ts +66 -0
- package/lib/types/utils/hash.d.ts +8 -4
- package/lib/types/version.d.ts +1 -1
- package/package.json +2 -1
package/lib/cjs/utils/hash.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
6
|
+
exports.convertDataStreamToDataRoot = exports.hashDataStreamToB64Url = exports.readableStreamToAsyncIterable = exports.isReadableStream = exports.isAsyncIterable = void 0;
|
|
4
7
|
/**
|
|
5
8
|
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
6
9
|
*
|
|
@@ -16,41 +19,90 @@ exports.hashBufferToB64Url = exports.hashReadableStreamToB64Url = exports.hashRe
|
|
|
16
19
|
* See the License for the specific language governing permissions and
|
|
17
20
|
* limitations under the License.
|
|
18
21
|
*/
|
|
22
|
+
const arweave_1 = __importDefault(require("arweave"));
|
|
23
|
+
const merkle_js_1 = require("arweave/node/lib/merkle.js");
|
|
19
24
|
const crypto_1 = require("crypto");
|
|
20
25
|
const base64_js_1 = require("./base64.js");
|
|
21
|
-
const
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
26
|
+
const isAsyncIterable = (x) => {
|
|
27
|
+
return x && typeof x[Symbol.asyncIterator] === 'function';
|
|
28
|
+
};
|
|
29
|
+
exports.isAsyncIterable = isAsyncIterable;
|
|
30
|
+
const isReadableStream = (x) => {
|
|
31
|
+
return x && typeof x.getReader === 'function';
|
|
28
32
|
};
|
|
29
|
-
exports.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
exports.isReadableStream = isReadableStream;
|
|
34
|
+
// convert ReadableStream to async iterable
|
|
35
|
+
const readableStreamToAsyncIterable = (stream) => ({
|
|
36
|
+
async *[Symbol.asyncIterator]() {
|
|
33
37
|
const reader = stream.getReader();
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
try {
|
|
39
|
+
while (true) {
|
|
36
40
|
const { done, value } = await reader.read();
|
|
37
|
-
if (done)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
hash.update(value);
|
|
42
|
-
read();
|
|
43
|
-
}
|
|
41
|
+
if (done)
|
|
42
|
+
break;
|
|
43
|
+
if (value !== undefined)
|
|
44
|
+
yield value;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
reader.releaseLock();
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
exports.readableStreamToAsyncIterable = readableStreamToAsyncIterable;
|
|
53
|
+
const hashDataStreamToB64Url = async (stream, algorithm = 'sha256') => {
|
|
54
|
+
const asyncIterable = (0, exports.isAsyncIterable)(stream)
|
|
55
|
+
? stream
|
|
56
|
+
: (0, exports.readableStreamToAsyncIterable)(stream);
|
|
57
|
+
const hash = (0, crypto_1.createHash)(algorithm);
|
|
58
|
+
for await (const chunk of asyncIterable) {
|
|
59
|
+
hash.update(chunk);
|
|
60
|
+
}
|
|
61
|
+
return (0, base64_js_1.toB64Url)(hash.digest());
|
|
51
62
|
};
|
|
52
|
-
exports.
|
|
53
|
-
const
|
|
54
|
-
|
|
63
|
+
exports.hashDataStreamToB64Url = hashDataStreamToB64Url;
|
|
64
|
+
const convertDataStreamToDataRoot = async ({ dataStream, }) => {
|
|
65
|
+
const chunks = [];
|
|
66
|
+
let leftover = new Uint8Array(0);
|
|
67
|
+
let cursor = 0;
|
|
68
|
+
const asyncIterable = (0, exports.isAsyncIterable)(dataStream)
|
|
69
|
+
? dataStream
|
|
70
|
+
: (0, exports.readableStreamToAsyncIterable)(dataStream);
|
|
71
|
+
for await (const data of asyncIterable) {
|
|
72
|
+
const inputChunk = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
73
|
+
const combined = new Uint8Array(leftover.length + inputChunk.length);
|
|
74
|
+
combined.set(leftover, 0);
|
|
75
|
+
combined.set(inputChunk, leftover.length);
|
|
76
|
+
let startIndex = 0;
|
|
77
|
+
while (combined.length - startIndex >= merkle_js_1.MAX_CHUNK_SIZE) {
|
|
78
|
+
let chunkSize = merkle_js_1.MAX_CHUNK_SIZE;
|
|
79
|
+
const remainderAfterThis = combined.length - startIndex - merkle_js_1.MAX_CHUNK_SIZE;
|
|
80
|
+
if (remainderAfterThis > 0 && remainderAfterThis < merkle_js_1.MIN_CHUNK_SIZE) {
|
|
81
|
+
chunkSize = Math.ceil((combined.length - startIndex) / 2);
|
|
82
|
+
}
|
|
83
|
+
const chunkData = combined.slice(startIndex, startIndex + chunkSize);
|
|
84
|
+
const dataHash = await arweave_1.default.crypto.hash(chunkData);
|
|
85
|
+
chunks.push({
|
|
86
|
+
dataHash,
|
|
87
|
+
minByteRange: cursor,
|
|
88
|
+
maxByteRange: cursor + chunkSize,
|
|
89
|
+
});
|
|
90
|
+
cursor += chunkSize;
|
|
91
|
+
startIndex += chunkSize;
|
|
92
|
+
}
|
|
93
|
+
leftover = combined.slice(startIndex);
|
|
94
|
+
}
|
|
95
|
+
if (leftover.length > 0) {
|
|
96
|
+
// TODO: ensure a web friendly crypto hash function is used in web
|
|
97
|
+
const dataHash = await arweave_1.default.crypto.hash(leftover);
|
|
98
|
+
chunks.push({
|
|
99
|
+
dataHash,
|
|
100
|
+
minByteRange: cursor,
|
|
101
|
+
maxByteRange: cursor + leftover.length,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const leaves = await (0, merkle_js_1.generateLeaves)(chunks);
|
|
105
|
+
const root = await (0, merkle_js_1.buildLayers)(leaves);
|
|
106
|
+
return (0, base64_js_1.toB64Url)(Buffer.from(root.id));
|
|
55
107
|
};
|
|
56
|
-
exports.
|
|
108
|
+
exports.convertDataStreamToDataRoot = convertDataStreamToDataRoot;
|
package/lib/cjs/version.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { assertConfirmationPrompt, assertEnoughBalanceForArNSPurchase, customTagsFromOptions, fundFromFromOptions, positiveIntegerFromOptions, recordTypeFromOptions, requiredPositiveIntegerFromOptions, requiredStringFromOptions, stringArrayFromOptions, writeARIOFromOptions, } from '../utils.js';
|
|
1
|
+
import { assertConfirmationPrompt, assertEnoughBalanceForArNSPurchase, customTagsFromOptions, fundFromFromOptions, positiveIntegerFromOptions, recordTypeFromOptions, referrerFromOptions, requiredPositiveIntegerFromOptions, requiredStringFromOptions, stringArrayFromOptions, writeARIOFromOptions, } from '../utils.js';
|
|
2
2
|
export async function buyRecordCLICommand(o) {
|
|
3
3
|
const { ario, signerAddress } = writeARIOFromOptions(o);
|
|
4
4
|
const name = requiredStringFromOptions(o, 'name');
|
|
5
5
|
const type = recordTypeFromOptions(o);
|
|
6
6
|
const years = positiveIntegerFromOptions(o, 'years');
|
|
7
7
|
const fundFrom = fundFromFromOptions(o);
|
|
8
|
+
const referrer = referrerFromOptions(o);
|
|
8
9
|
const processId = o.processId;
|
|
9
10
|
if (processId === undefined) {
|
|
10
11
|
// TODO: Spawn ANT process, register it to ANT registry, get process ID
|
|
@@ -38,12 +39,14 @@ export async function buyRecordCLICommand(o) {
|
|
|
38
39
|
years,
|
|
39
40
|
fundFrom: fundFromFromOptions(o),
|
|
40
41
|
paidBy: stringArrayFromOptions(o, 'paidBy'),
|
|
42
|
+
referrer,
|
|
41
43
|
}, customTagsFromOptions(o));
|
|
42
44
|
}
|
|
43
45
|
export async function upgradeRecordCLICommand(o) {
|
|
44
46
|
const name = requiredStringFromOptions(o, 'name');
|
|
45
47
|
const { ario, signerAddress } = writeARIOFromOptions(o);
|
|
46
48
|
const fundFrom = fundFromFromOptions(o);
|
|
49
|
+
const referrer = referrerFromOptions(o);
|
|
47
50
|
if (!o.skipConfirmation) {
|
|
48
51
|
const existingRecord = await ario.getArNSRecord({
|
|
49
52
|
name,
|
|
@@ -70,12 +73,14 @@ export async function upgradeRecordCLICommand(o) {
|
|
|
70
73
|
name,
|
|
71
74
|
fundFrom,
|
|
72
75
|
paidBy: stringArrayFromOptions(o, 'paidBy'),
|
|
76
|
+
referrer,
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
79
|
export async function extendLeaseCLICommand(o) {
|
|
76
80
|
const name = requiredStringFromOptions(o, 'name');
|
|
77
81
|
const years = requiredPositiveIntegerFromOptions(o, 'years');
|
|
78
82
|
const fundFrom = fundFromFromOptions(o);
|
|
83
|
+
const referrer = referrerFromOptions(o);
|
|
79
84
|
const { ario, signerAddress } = writeARIOFromOptions(o);
|
|
80
85
|
if (!o.skipConfirmation) {
|
|
81
86
|
const existingRecord = await ario.getArNSRecord({
|
|
@@ -104,12 +109,14 @@ export async function extendLeaseCLICommand(o) {
|
|
|
104
109
|
name,
|
|
105
110
|
years,
|
|
106
111
|
paidBy: stringArrayFromOptions(o, 'paidBy'),
|
|
112
|
+
referrer,
|
|
107
113
|
}, customTagsFromOptions(o));
|
|
108
114
|
}
|
|
109
115
|
export async function increaseUndernameLimitCLICommand(o) {
|
|
110
116
|
const name = requiredStringFromOptions(o, 'name');
|
|
111
117
|
const increaseCount = requiredPositiveIntegerFromOptions(o, 'increaseCount');
|
|
112
118
|
const fundFrom = fundFromFromOptions(o);
|
|
119
|
+
const referrer = referrerFromOptions(o);
|
|
113
120
|
const { ario, signerAddress } = writeARIOFromOptions(o);
|
|
114
121
|
if (!o.skipConfirmation) {
|
|
115
122
|
const existingRecord = await ario.getArNSRecord({
|
|
@@ -135,11 +142,13 @@ export async function increaseUndernameLimitCLICommand(o) {
|
|
|
135
142
|
name,
|
|
136
143
|
increaseCount,
|
|
137
144
|
paidBy: stringArrayFromOptions(o, 'paidBy'),
|
|
145
|
+
referrer,
|
|
138
146
|
}, customTagsFromOptions(o));
|
|
139
147
|
}
|
|
140
148
|
export async function requestPrimaryNameCLICommand(o) {
|
|
141
149
|
const { ario, signerAddress } = writeARIOFromOptions(o);
|
|
142
150
|
const fundFrom = fundFromFromOptions(o);
|
|
151
|
+
const referrer = referrerFromOptions(o);
|
|
143
152
|
const name = requiredStringFromOptions(o, 'name');
|
|
144
153
|
if (!o.skipConfirmation) {
|
|
145
154
|
// TODO: Assert name requested is not already owned?
|
|
@@ -160,5 +169,6 @@ export async function requestPrimaryNameCLICommand(o) {
|
|
|
160
169
|
name,
|
|
161
170
|
fundFrom,
|
|
162
171
|
paidBy: stringArrayFromOptions(o, 'paidBy'),
|
|
172
|
+
referrer,
|
|
163
173
|
}, customTagsFromOptions(o));
|
|
164
174
|
}
|
package/lib/esm/cli/options.js
CHANGED
|
@@ -283,6 +283,10 @@ export const optionMap = {
|
|
|
283
283
|
description: 'Addresses to pay for the interaction',
|
|
284
284
|
type: 'array',
|
|
285
285
|
},
|
|
286
|
+
referrer: {
|
|
287
|
+
alias: '--referrer <referrer>',
|
|
288
|
+
description: 'The referrer for ArNS purchase tracking',
|
|
289
|
+
},
|
|
286
290
|
};
|
|
287
291
|
export const walletOptions = [
|
|
288
292
|
optionMap.walletFile,
|
|
@@ -306,6 +310,7 @@ export const arnsPurchaseOptions = [
|
|
|
306
310
|
optionMap.fundFrom,
|
|
307
311
|
optionMap.paidBy,
|
|
308
312
|
optionMap.paymentUrl,
|
|
313
|
+
optionMap.referrer,
|
|
309
314
|
];
|
|
310
315
|
export const epochOptions = [optionMap.epochIndex, optionMap.timestamp];
|
|
311
316
|
export const addressAndVaultIdOptions = [optionMap.address, optionMap.vaultId];
|
package/lib/esm/cli/utils.js
CHANGED
|
@@ -458,6 +458,9 @@ export function fundFromFromOptions(o) {
|
|
|
458
458
|
}
|
|
459
459
|
return o.fundFrom ?? 'balance';
|
|
460
460
|
}
|
|
461
|
+
export function referrerFromOptions(o) {
|
|
462
|
+
return o.referrer;
|
|
463
|
+
}
|
|
461
464
|
export function assertLockLengthInRange(lockLengthMs, assertMin = true) {
|
|
462
465
|
const minLockLengthMs = 1209600000; // 14 days
|
|
463
466
|
const maxLockLengthMs = 378432000000; // ~12 years
|
package/lib/esm/common/index.js
CHANGED
package/lib/esm/common/io.js
CHANGED
|
@@ -981,6 +981,7 @@ export class ARIOWriteable extends ARIOReadable {
|
|
|
981
981
|
{ name: 'Process-Id', value: params.processId },
|
|
982
982
|
{ name: 'Purchase-Type', value: params.type || 'lease' },
|
|
983
983
|
{ name: 'Fund-From', value: params.fundFrom },
|
|
984
|
+
{ name: 'Referrer', value: params.referrer },
|
|
984
985
|
];
|
|
985
986
|
return this.process.send({
|
|
986
987
|
signer: this.signer,
|
|
@@ -1011,6 +1012,7 @@ export class ARIOWriteable extends ARIOReadable {
|
|
|
1011
1012
|
{ name: 'Action', value: 'Upgrade-Name' },
|
|
1012
1013
|
{ name: 'Name', value: params.name },
|
|
1013
1014
|
{ name: 'Fund-From', value: params.fundFrom },
|
|
1015
|
+
{ name: 'Referrer', value: params.referrer },
|
|
1014
1016
|
];
|
|
1015
1017
|
return this.process.send({
|
|
1016
1018
|
signer: this.signer,
|
|
@@ -1044,6 +1046,7 @@ export class ARIOWriteable extends ARIOReadable {
|
|
|
1044
1046
|
{ name: 'Name', value: params.name },
|
|
1045
1047
|
{ name: 'Years', value: params.years.toString() },
|
|
1046
1048
|
{ name: 'Fund-From', value: params.fundFrom },
|
|
1049
|
+
{ name: 'Referrer', value: params.referrer },
|
|
1047
1050
|
];
|
|
1048
1051
|
return this.process.send({
|
|
1049
1052
|
signer: this.signer,
|
|
@@ -1068,6 +1071,7 @@ export class ARIOWriteable extends ARIOReadable {
|
|
|
1068
1071
|
{ name: 'Name', value: params.name },
|
|
1069
1072
|
{ name: 'Quantity', value: params.increaseCount.toString() },
|
|
1070
1073
|
{ name: 'Fund-From', value: params.fundFrom },
|
|
1074
|
+
{ name: 'Referrer', value: params.referrer },
|
|
1071
1075
|
];
|
|
1072
1076
|
return this.process.send({
|
|
1073
1077
|
signer: this.signer,
|
package/lib/esm/common/turbo.js
CHANGED
|
@@ -148,7 +148,7 @@ export class TurboArNSPaymentProviderAuthenticated extends TurboArNSPaymentProvi
|
|
|
148
148
|
}
|
|
149
149
|
this.signer = signer;
|
|
150
150
|
}
|
|
151
|
-
async initiateArNSPurchase({ intent, name, quantity, type, processId, years, paidBy = [], }) {
|
|
151
|
+
async initiateArNSPurchase({ intent, name, quantity, type, processId, years, paidBy = [], referrer, }) {
|
|
152
152
|
// Signer check is implicitly handled by requiring it in the constructor
|
|
153
153
|
const url = urlWithSearchParams({
|
|
154
154
|
baseUrl: `${this.paymentUrl}/v1/arns/purchase/${intent}/${name}`,
|
|
@@ -158,6 +158,7 @@ export class TurboArNSPaymentProviderAuthenticated extends TurboArNSPaymentProvi
|
|
|
158
158
|
type,
|
|
159
159
|
years,
|
|
160
160
|
paidBy,
|
|
161
|
+
referrer,
|
|
161
162
|
},
|
|
162
163
|
});
|
|
163
164
|
const headers = await signedRequestHeadersFromSigner({
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ARIO } from '../../io.js';
|
|
2
|
+
export class NetworkGatewaysProvider {
|
|
3
|
+
ario;
|
|
4
|
+
sortBy;
|
|
5
|
+
sortOrder;
|
|
6
|
+
limit;
|
|
7
|
+
filter;
|
|
8
|
+
constructor({ ario = ARIO.mainnet(), sortBy = 'operatorStake', sortOrder = 'desc', limit = 1000, filter = (g) => g.status === 'joined', }) {
|
|
9
|
+
this.ario = ario;
|
|
10
|
+
this.sortBy = sortBy;
|
|
11
|
+
this.sortOrder = sortOrder;
|
|
12
|
+
this.limit = limit;
|
|
13
|
+
this.filter = filter;
|
|
14
|
+
}
|
|
15
|
+
async getGateways() {
|
|
16
|
+
let cursor;
|
|
17
|
+
let attempts = 0;
|
|
18
|
+
const gateways = [];
|
|
19
|
+
do {
|
|
20
|
+
try {
|
|
21
|
+
const { items: newGateways = [], nextCursor } = await this.ario.getGateways({
|
|
22
|
+
limit: 1000,
|
|
23
|
+
cursor,
|
|
24
|
+
sortBy: this.sortBy,
|
|
25
|
+
sortOrder: this.sortOrder,
|
|
26
|
+
});
|
|
27
|
+
gateways.push(...newGateways);
|
|
28
|
+
cursor = nextCursor;
|
|
29
|
+
attempts = 0; // reset attempts if we get a new cursor
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error('Error fetching gateways', {
|
|
33
|
+
cursor,
|
|
34
|
+
attempts,
|
|
35
|
+
error,
|
|
36
|
+
});
|
|
37
|
+
attempts++;
|
|
38
|
+
}
|
|
39
|
+
} while (cursor !== undefined && attempts < 3);
|
|
40
|
+
// filter out any gateways that are not joined
|
|
41
|
+
const filteredGateways = gateways.filter(this.filter).slice(0, this.limit);
|
|
42
|
+
return filteredGateways.map((g) => new URL(`${g.settings.protocol}://${g.settings.fqdn}:${g.settings.port}`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Logger } from '../../../web/index.js';
|
|
2
|
+
export class SimpleCacheGatewaysProvider {
|
|
3
|
+
gatewaysProvider;
|
|
4
|
+
ttlSeconds;
|
|
5
|
+
lastUpdated;
|
|
6
|
+
gatewaysCache;
|
|
7
|
+
logger;
|
|
8
|
+
constructor({ gatewaysProvider, ttlSeconds = 60 * 60, // 1 hour
|
|
9
|
+
logger = Logger.default, }) {
|
|
10
|
+
this.gatewaysCache = [];
|
|
11
|
+
this.gatewaysProvider = gatewaysProvider;
|
|
12
|
+
this.ttlSeconds = ttlSeconds;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
}
|
|
15
|
+
async getGateways() {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
if (this.gatewaysCache.length === 0 ||
|
|
18
|
+
now - this.lastUpdated > this.ttlSeconds * 1000) {
|
|
19
|
+
try {
|
|
20
|
+
// preserve the cache if the fetch fails
|
|
21
|
+
const allGateways = await this.gatewaysProvider.getGateways();
|
|
22
|
+
this.gatewaysCache = allGateways;
|
|
23
|
+
this.lastUpdated = now;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
this.logger.error('Error fetching gateways', error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return this.gatewaysCache;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
export * from './wayfinder.js';
|
|
17
|
+
// routing strategies
|
|
18
|
+
export * from './routing/strategies/random.js';
|
|
19
|
+
export * from './routing/strategies/static.js';
|
|
20
|
+
export * from './routing/strategies/ping.js';
|
|
21
|
+
export * from './routing/strategies/round-robin.js';
|
|
22
|
+
export * from './routing/strategies/preferred-with-fallback.js';
|
|
23
|
+
// gateways providers
|
|
24
|
+
export * from './gateways/network.js';
|
|
25
|
+
export * from './gateways/simple-cache.js';
|
|
26
|
+
export * from './gateways/static.js';
|
|
27
|
+
// trusted gateways
|
|
28
|
+
export * from './verification/trusted.js';
|
|
29
|
+
// hash providers
|
|
30
|
+
export * from './verification/strategies/data-root-verifier.js';
|
|
31
|
+
export * from './verification/strategies/hash-verifier.js';
|
|
32
|
+
// TODO: signature verification
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class FastestPingRoutingStrategy {
|
|
2
|
+
timeoutMs;
|
|
3
|
+
probePath;
|
|
4
|
+
constructor({ timeoutMs = 500, probePath = '/ar-io/info', // TODO: limit to allowed /ar-io and arweave node endpoints
|
|
5
|
+
} = {}) {
|
|
6
|
+
this.timeoutMs = timeoutMs;
|
|
7
|
+
this.probePath = probePath;
|
|
8
|
+
}
|
|
9
|
+
async selectGateway({ gateways }) {
|
|
10
|
+
if (gateways.length === 0) {
|
|
11
|
+
throw new Error('No gateways provided');
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const results = await Promise.allSettled(gateways.map(async (gateway) => {
|
|
15
|
+
try {
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
const response = await fetch(`${gateway.toString().replace(/\/$/, '')}${this.probePath}`, {
|
|
18
|
+
method: 'HEAD',
|
|
19
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
20
|
+
});
|
|
21
|
+
const endTime = Date.now();
|
|
22
|
+
const durationMs = endTime - startTime;
|
|
23
|
+
return {
|
|
24
|
+
gateway,
|
|
25
|
+
status: response.status,
|
|
26
|
+
durationMs,
|
|
27
|
+
error: null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// Handle network errors
|
|
32
|
+
return {
|
|
33
|
+
gateway,
|
|
34
|
+
status: 'rejected',
|
|
35
|
+
durationMs: Infinity,
|
|
36
|
+
error,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
// Process results
|
|
41
|
+
const processedResults = results.map((result, index) => {
|
|
42
|
+
if (result.status === 'fulfilled') {
|
|
43
|
+
return result.value;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return {
|
|
47
|
+
gateway: gateways[index],
|
|
48
|
+
status: 'rejected',
|
|
49
|
+
durationMs: Infinity,
|
|
50
|
+
error: result.reason,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Filter healthy gateways and sort by latency
|
|
55
|
+
const healthyGateways = processedResults
|
|
56
|
+
.filter((result) => result.status === 200)
|
|
57
|
+
.sort((a, b) => a.durationMs - b.durationMs);
|
|
58
|
+
if (healthyGateways.length > 0) {
|
|
59
|
+
return healthyGateways[0].gateway;
|
|
60
|
+
}
|
|
61
|
+
throw new Error('No healthy gateways found');
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new Error('Failed to ping gateways: ' +
|
|
65
|
+
(error instanceof Error ? error.message : String(error)));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Logger } from '../../../logger.js';
|
|
2
|
+
import { FastestPingRoutingStrategy } from './ping.js';
|
|
3
|
+
export class PreferredWithFallbackRoutingStrategy {
|
|
4
|
+
name = 'preferred-with-fallback';
|
|
5
|
+
preferredGateway;
|
|
6
|
+
fallbackStrategy;
|
|
7
|
+
logger;
|
|
8
|
+
constructor({ preferredGateway, fallbackStrategy = new FastestPingRoutingStrategy(), logger = Logger.default, }) {
|
|
9
|
+
try {
|
|
10
|
+
this.preferredGateway = new URL(preferredGateway);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
throw new Error(`Invalid URL provided for preferred gateway: ${preferredGateway}`);
|
|
14
|
+
}
|
|
15
|
+
this.fallbackStrategy = fallbackStrategy;
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
}
|
|
18
|
+
async selectGateway({ gateways = [] }) {
|
|
19
|
+
this.logger.debug('Attempting to connect to preferred gateway', {
|
|
20
|
+
preferredGateway: this.preferredGateway.toString(),
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
// Check if the preferred gateway is responsive
|
|
24
|
+
const response = await fetch(this.preferredGateway.toString(), {
|
|
25
|
+
method: 'HEAD',
|
|
26
|
+
signal: AbortSignal.timeout(1000),
|
|
27
|
+
});
|
|
28
|
+
if (response.ok) {
|
|
29
|
+
this.logger.debug('Successfully connected to preferred gateway', {
|
|
30
|
+
preferredGateway: this.preferredGateway.toString(),
|
|
31
|
+
});
|
|
32
|
+
return this.preferredGateway;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Preferred gateway responded with status: ${response.status}`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.logger.warn('Failed to connect to preferred gateway, falling back to alternative strategy', {
|
|
38
|
+
preferredGateway: this.preferredGateway.toString(),
|
|
39
|
+
error: error instanceof Error ? error.message : String(error),
|
|
40
|
+
fallbackStrategy: this.fallbackStrategy.constructor.name,
|
|
41
|
+
});
|
|
42
|
+
// Fall back to the provided routing strategy
|
|
43
|
+
return this.fallbackStrategy.selectGateway({ gateways });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { randomInt } from '../../../../utils/random.js';
|
|
2
|
+
export class RandomRoutingStrategy {
|
|
3
|
+
async selectGateway({ gateways }) {
|
|
4
|
+
if (gateways.length === 0) {
|
|
5
|
+
throw new Error('No gateways available');
|
|
6
|
+
}
|
|
7
|
+
return gateways[randomInt(0, gateways.length)];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { Logger } from '../../../../common/logger.js';
|
|
17
|
+
export class RoundRobinRoutingStrategy {
|
|
18
|
+
gateways;
|
|
19
|
+
currentIndex;
|
|
20
|
+
logger;
|
|
21
|
+
constructor({ gateways, logger = Logger.default, }) {
|
|
22
|
+
this.gateways = gateways;
|
|
23
|
+
this.currentIndex = 0;
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
}
|
|
26
|
+
// provided gateways are ignored
|
|
27
|
+
async selectGateway({ gateways = [], } = {}) {
|
|
28
|
+
if (gateways.length > 0) {
|
|
29
|
+
this.logger.warn('RoundRobinRoutingStrategy does not accept provided gateways. Ignoring provided gateways...', {
|
|
30
|
+
providedGateways: gateways.length,
|
|
31
|
+
internalGateways: this.gateways,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const gateway = this.gateways[this.currentIndex];
|
|
35
|
+
this.currentIndex = (this.currentIndex + 1) % this.gateways.length;
|
|
36
|
+
return gateway;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Logger } from '../../../logger.js';
|
|
2
|
+
export class StaticRoutingStrategy {
|
|
3
|
+
name = 'static';
|
|
4
|
+
gateway;
|
|
5
|
+
logger;
|
|
6
|
+
constructor({ gateway, logger = Logger.default, }) {
|
|
7
|
+
try {
|
|
8
|
+
this.gateway = new URL(gateway);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw new Error(`Invalid URL provided for static gateway: ${gateway}`);
|
|
12
|
+
}
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
}
|
|
15
|
+
// provided gateways are ignored
|
|
16
|
+
async selectGateway({ gateways = [], } = {}) {
|
|
17
|
+
if (gateways.length > 0) {
|
|
18
|
+
this.logger.warn('StaticRoutingStrategy does not accept provided gateways. Ignoring provided gateways...', {
|
|
19
|
+
providedGateways: gateways.length,
|
|
20
|
+
internalGateway: this.gateway,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return this.gateway;
|
|
24
|
+
}
|
|
25
|
+
}
|