Skip to content

Commit

Permalink
fix(arns): enforce undername limit
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dtfiedler committed Nov 14, 2024
1 parent 0d3e2a9 commit e07f53c
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 10 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"url": "https://github.com/ar-io/ar-io-node"
},
"dependencies": {
"@ar.io/sdk": "^2.2.5",
"@ar.io/sdk": "^2.5.0-alpha.2",
"@aws-lite/client": "^0.21.7",
"@aws-lite/s3": "^0.1.21",
"@clickhouse/client": "^1.3.0",
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const headerNames = {
arnsResolvedId: 'X-ArNS-Resolved-Id',
arnsProcessId: 'X-ArNS-Process-Id',
arnsResolvedAt: 'X-ArNS-Resolved-At',
arnsLimit: 'X-ArNS-Limit',
arnsIndex: 'X-ArNS-Index',
};

export const DATA_PATH_REGEX =
Expand Down
18 changes: 16 additions & 2 deletions src/middleware/arns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { asyncMiddleware } from 'middleware-async';

import * as config from '../config.js';
import { headerNames } from '../constants.js';
import { sendNotFound } from '../routes/data/handlers.js';
import { sendNotFound, sendPaymentRequired } from '../routes/data/handlers.js';
import { DATA_PATH_REGEX } from '../constants.js';
import { NameResolution, NameResolver } from '../types.js';
import * as metrics from '../metrics.js';
Expand Down Expand Up @@ -90,20 +90,34 @@ export const createArnsMiddleware = ({
};

const start = Date.now();
const { resolvedId, ttl, processId, resolvedAt } =
const { resolvedId, ttl, processId, resolvedAt, limit, index } =
await getArnsResolutionPromise().finally(() => {
// remove from cache after resolution
arnsRequestCache.del(arnsSubdomain);
});
metrics.arnsResolutionTime.observe(Date.now() - start);
// TODO: handle if undername is not supported based on undername limits and return a 402 error
if (resolvedId === undefined) {
sendNotFound(res);
return;
}

res.header(headerNames.arnsResolvedId, resolvedId);
res.header(headerNames.arnsTtlSeconds, ttl.toString());
res.header(headerNames.arnsProcessId, processId);
res.header(headerNames.arnsResolvedAt, resolvedAt.toString());
res.header(headerNames.arnsLimit, limit.toString());
res.header(headerNames.arnsIndex, index.toString());

// handle undername limit exceeded
if (index > limit) {
sendPaymentRequired(
res,
'ArNS undername limit exceeded. Purchase additional undernames to continue.',
);
return;
}

// TODO: add a header for arns cache status
res.header('Cache-Control', `public, max-age=${ttl}`);
dataHandler(req, res, next);
Expand Down
2 changes: 2 additions & 0 deletions src/resolution/composite-arns-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export class CompositeArNSResolver implements NameResolver {
resolvedAt: undefined,
ttl: undefined,
processId: undefined,
limit: undefined,
index: undefined,
}
);
}
Expand Down
22 changes: 19 additions & 3 deletions src/resolution/on-demand-arns-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export class OnDemandArNSResolver implements NameResolver {
resolvedAt: Date.now(),
ttl: 300,
processId: undefined,
limit: undefined,
index: undefined,
};
}

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

const antRecord = await ant.getRecord({
undername,
});
// enforce the limit of undername resolution, the ant contract is responsible for returning names in the order they should be resolved
const antRecords = await ant.getRecords();

const nameIndex = antRecords.findIndex(
(record) => record.name === undername,
);

// validate the undername exists on the ant records
if (nameIndex === -1) {
throw new Error('Undername does not exist on the ant records');
}

const antRecord = antRecords[nameIndex];

if (antRecord === undefined) {
throw new Error('Invalid name, ant record for name not found');
Expand All @@ -139,6 +151,8 @@ export class OnDemandArNSResolver implements NameResolver {
resolvedAt: Date.now(),
processId: processId,
ttl,
limit: arnsRecord.undernameLimit,
index: nameIndex,
};
} catch (error: any) {
this.log.warn('Unable to resolve name:', {
Expand All @@ -154,6 +168,8 @@ export class OnDemandArNSResolver implements NameResolver {
resolvedAt: undefined,
ttl: undefined,
processId: undefined,
limit: undefined,
index: undefined,
};
}
}
10 changes: 10 additions & 0 deletions src/resolution/trusted-gateway-arns-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export class TrustedGatewayArNSResolver implements NameResolver {
const ttl =
parseInt(response.headers[headerNames.arnsTtlSeconds.toLowerCase()]) ||
DEFAULT_ARNS_TTL_SECONDS;
const limit =
parseInt(response.headers[headerNames.arnsLimit.toLowerCase()]) || 10;
const index =
parseInt(response.headers[headerNames.arnsIndex.toLowerCase()]) || 0;
if (isValidDataId(resolvedId)) {
this.log.info('Resolved name', { name, nameUrl, resolvedId, ttl });
return {
Expand All @@ -64,13 +68,17 @@ export class TrustedGatewayArNSResolver implements NameResolver {
resolvedAt: Date.now(),
processId,
ttl,
limit,
index,
};
} else {
this.log.warn('Invalid resolved data ID', {
name,
nameUrl,
resolvedId,
ttl,
limit,
index,
});
}
} catch (error: any) {
Expand All @@ -87,6 +95,8 @@ export class TrustedGatewayArNSResolver implements NameResolver {
resolvedAt: undefined,
ttl: undefined,
processId: undefined,
limit: undefined,
index: undefined,
};
}
}
7 changes: 7 additions & 0 deletions src/routes/data/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ export const sendNotFound = (res: Response) => {
res.status(404).send('Not found');
};

export const sendPaymentRequired = (
res: Response,
text = 'Payment required',
) => {
res.status(402).send(text);
};

// Data routes
export const createRawDataHandler = ({
log,
Expand Down
6 changes: 6 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,8 @@ export interface ValidNameResolution {
resolvedAt: number;
ttl: number;
processId: string;
limit: number;
index: number;
}

// Name resolved, but is missing
Expand All @@ -612,6 +614,8 @@ export interface MissingNameResolution {
resolvedAt: number;
ttl: number;
processId: undefined;
limit: undefined;
index: undefined;
}

// An error occured while resolving the name
Expand All @@ -621,6 +625,8 @@ export interface FailedNameResolution {
resolvedAt: undefined;
ttl: undefined;
processId: undefined;
limit: undefined;
index: undefined;
}

type NameResolution =
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@
dependencies:
xss "^1.0.8"

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

0 comments on commit e07f53c

Please sign in to comment.