Skip to content

Commit 7f8893a

Browse files
author
dtfiedler
committed
fix(arns): enforce undername limit
The ANTs provide the sorted array of records in resolution order. We are now enforcing the undername limits and returning a 402 to arns names that are not supported. Additional `X-ArNS-Limit` and `X-ArNS-Index` headers are added to indicate where the name/undername is in the list of records returned by the ANT.
1 parent 0d3e2a9 commit 7f8893a

File tree

9 files changed

+66
-10
lines changed

9 files changed

+66
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"url": "https://github.com/ar-io/ar-io-node"
99
},
1010
"dependencies": {
11-
"@ar.io/sdk": "^2.2.5",
11+
"@ar.io/sdk": "^2.5.0-alpha.2",
1212
"@aws-lite/client": "^0.21.7",
1313
"@aws-lite/s3": "^0.1.21",
1414
"@clickhouse/client": "^1.3.0",

src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export const headerNames = {
3030
arnsResolvedId: 'X-ArNS-Resolved-Id',
3131
arnsProcessId: 'X-ArNS-Process-Id',
3232
arnsResolvedAt: 'X-ArNS-Resolved-At',
33+
arnsLimit: 'X-ArNS-Limit',
34+
arnsIndex: 'X-ArNS-Index',
3335
};
3436

3537
export const DATA_PATH_REGEX =

src/middleware/arns.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { asyncMiddleware } from 'middleware-async';
2020

2121
import * as config from '../config.js';
2222
import { headerNames } from '../constants.js';
23-
import { sendNotFound } from '../routes/data/handlers.js';
23+
import { sendNotFound, sendPaymentRequired } from '../routes/data/handlers.js';
2424
import { DATA_PATH_REGEX } from '../constants.js';
2525
import { NameResolution, NameResolver } from '../types.js';
2626
import * as metrics from '../metrics.js';
@@ -90,7 +90,7 @@ export const createArnsMiddleware = ({
9090
};
9191

9292
const start = Date.now();
93-
const { resolvedId, ttl, processId, resolvedAt } =
93+
const { resolvedId, ttl, processId, resolvedAt, limit, index } =
9494
await getArnsResolutionPromise().finally(() => {
9595
// remove from cache after resolution
9696
arnsRequestCache.del(arnsSubdomain);
@@ -100,10 +100,23 @@ export const createArnsMiddleware = ({
100100
sendNotFound(res);
101101
return;
102102
}
103+
103104
res.header(headerNames.arnsResolvedId, resolvedId);
104105
res.header(headerNames.arnsTtlSeconds, ttl.toString());
105106
res.header(headerNames.arnsProcessId, processId);
106107
res.header(headerNames.arnsResolvedAt, resolvedAt.toString());
108+
res.header(headerNames.arnsLimit, limit.toString());
109+
res.header(headerNames.arnsIndex, index.toString());
110+
111+
// handle undername limit exceeded
112+
if (index > limit) {
113+
sendPaymentRequired(
114+
res,
115+
'ArNS undername limit exceeded. Purchase additional undernames to continue.',
116+
);
117+
return;
118+
}
119+
107120
// TODO: add a header for arns cache status
108121
res.header('Cache-Control', `public, max-age=${ttl}`);
109122
dataHandler(req, res, next);

src/resolution/composite-arns-resolver.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ export class CompositeArNSResolver implements NameResolver {
113113
resolvedAt: undefined,
114114
ttl: undefined,
115115
processId: undefined,
116+
limit: undefined,
117+
index: undefined,
116118
}
117119
);
118120
}

src/resolution/on-demand-arns-resolver.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export class OnDemandArNSResolver implements NameResolver {
102102
resolvedAt: Date.now(),
103103
ttl: 300,
104104
processId: undefined,
105+
limit: undefined,
106+
index: undefined,
105107
};
106108
}
107109

@@ -119,9 +121,19 @@ export class OnDemandArNSResolver implements NameResolver {
119121
const undername =
120122
name === baseName ? '@' : name.replace(`_${baseName}`, '');
121123

122-
const antRecord = await ant.getRecord({
123-
undername,
124-
});
124+
// enforce the limit of undername resolution, the ant contract is responsible for returning names in the order they should be resolved
125+
const antRecords = await ant.getRecords();
126+
127+
const nameIndex = antRecords.findIndex(
128+
(record) => record.name === undername,
129+
);
130+
131+
// validate the undername exists on the ant records
132+
if (nameIndex === -1) {
133+
throw new Error('Undername does not exist on the ant records');
134+
}
135+
136+
const antRecord = antRecords[nameIndex];
125137

126138
if (antRecord === undefined) {
127139
throw new Error('Invalid name, ant record for name not found');
@@ -139,6 +151,8 @@ export class OnDemandArNSResolver implements NameResolver {
139151
resolvedAt: Date.now(),
140152
processId: processId,
141153
ttl,
154+
limit: arnsRecord.undernameLimit,
155+
index: nameIndex,
142156
};
143157
} catch (error: any) {
144158
this.log.warn('Unable to resolve name:', {
@@ -154,6 +168,8 @@ export class OnDemandArNSResolver implements NameResolver {
154168
resolvedAt: undefined,
155169
ttl: undefined,
156170
processId: undefined,
171+
limit: undefined,
172+
index: undefined,
157173
};
158174
}
159175
}

src/resolution/trusted-gateway-arns-resolver.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export class TrustedGatewayArNSResolver implements NameResolver {
5656
const ttl =
5757
parseInt(response.headers[headerNames.arnsTtlSeconds.toLowerCase()]) ||
5858
DEFAULT_ARNS_TTL_SECONDS;
59+
const limit =
60+
parseInt(response.headers[headerNames.arnsLimit.toLowerCase()]) || 10;
61+
const index =
62+
parseInt(response.headers[headerNames.arnsIndex.toLowerCase()]) || 0;
5963
if (isValidDataId(resolvedId)) {
6064
this.log.info('Resolved name', { name, nameUrl, resolvedId, ttl });
6165
return {
@@ -64,13 +68,17 @@ export class TrustedGatewayArNSResolver implements NameResolver {
6468
resolvedAt: Date.now(),
6569
processId,
6670
ttl,
71+
limit,
72+
index,
6773
};
6874
} else {
6975
this.log.warn('Invalid resolved data ID', {
7076
name,
7177
nameUrl,
7278
resolvedId,
7379
ttl,
80+
limit,
81+
index,
7482
});
7583
}
7684
} catch (error: any) {
@@ -87,6 +95,8 @@ export class TrustedGatewayArNSResolver implements NameResolver {
8795
resolvedAt: undefined,
8896
ttl: undefined,
8997
processId: undefined,
98+
limit: undefined,
99+
index: undefined,
90100
};
91101
}
92102
}

src/routes/data/handlers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,13 @@ export const sendNotFound = (res: Response) => {
331331
res.status(404).send('Not found');
332332
};
333333

334+
export const sendPaymentRequired = (
335+
res: Response,
336+
text = 'Payment required',
337+
) => {
338+
res.status(402).send(text);
339+
};
340+
334341
// Data routes
335342
export const createRawDataHandler = ({
336343
log,

src/types.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ export interface ValidNameResolution {
603603
resolvedAt: number;
604604
ttl: number;
605605
processId: string;
606+
limit: number;
607+
index: number;
606608
}
607609

608610
// Name resolved, but is missing
@@ -612,6 +614,8 @@ export interface MissingNameResolution {
612614
resolvedAt: number;
613615
ttl: number;
614616
processId: undefined;
617+
limit: undefined;
618+
index: undefined;
615619
}
616620

617621
// An error occured while resolving the name
@@ -621,6 +625,8 @@ export interface FailedNameResolution {
621625
resolvedAt: undefined;
622626
ttl: undefined;
623627
processId: undefined;
628+
limit: undefined;
629+
index: undefined;
624630
}
625631

626632
type NameResolution =

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,10 @@
119119
dependencies:
120120
xss "^1.0.8"
121121

122-
"@ar.io/sdk@^2.2.5":
123-
version "2.3.2"
124-
resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.3.2.tgz#afac6c5cb38f517f53af6c9d70eeea9175fc70c4"
125-
integrity sha512-O1BX951DzwRB3/9hc8O8PulxE84qe6wSN3ADqlJT4W0k9RcWLN/rbGMdSPaoN8dMgnxwtnIkXkHw6CG9Fu+V3g==
122+
"@ar.io/sdk@^2.5.0-alpha.2":
123+
version "2.5.0-alpha.2"
124+
resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.5.0-alpha.2.tgz#dafa3649988691ac06c33afc53481c8b0b827a2f"
125+
integrity sha512-vH8gF7P4qurGewYc38J2ari/o6Q7Hy9a6IGG8BLa96xzETxG8uCznw9widuuWxObCvzDp0DGIBxQPlrvsQxauA==
126126
dependencies:
127127
"@dha-team/arbundles" "^1.0.1"
128128
"@permaweb/aoconnect" "^0.0.57"

0 commit comments

Comments
 (0)