Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(arns): enforce undername limit #234

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ services:
- ARNS_CACHE_TTL_SECONDS=${ARNS_CACHE_TTL_SECONDS:-3600}
- ARNS_CACHE_MAX_KEYS=${ARNS_CACHE_MAX_KEYS:-10000}
- ARNS_CACHE_TYPE=${ARNS_CACHE_TYPE:-redis}
- ARNS_RESOLVER_ENFORCE_UNDERNAME_LIMIT=${ARNS_RESOLVER_ENFORCE_UNDERNAME_LIMIT:-false}
- ENABLE_MEMPOOL_WATCHER=${ENABLE_MEMPOOL_WATCHER:-false}
- MEMPOOL_POOLING_INTERVAL_MS=${MEMPOOL_POOLING_INTERVAL_MS:-}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
Expand Down
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.8",
"@aws-lite/client": "^0.21.7",
"@aws-lite/s3": "^0.1.21",
"@clickhouse/client": "^1.3.0",
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ export const ARNS_RESOLVER_OVERRIDE_TTL_SECONDS_STRING = env.varOrUndefined(
'ARNS_RESOLVER_OVERRIDE_TTL_SECONDS',
);

export const ARNS_RESOLVER_ENFORCE_UNDERNAME_LIMIT =
env.varOrDefault('ARNS_RESOLVER_ENFORCE_UNDERNAME_LIMIT', 'false') === 'true';

export const ARNS_RESOLVER_OVERRIDE_TTL_SECONDS =
ARNS_RESOLVER_OVERRIDE_TTL_SECONDS_STRING !== undefined
? +ARNS_RESOLVER_OVERRIDE_TTL_SECONDS_STRING
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
17 changes: 15 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,7 +90,7 @@ 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);
Expand All @@ -100,10 +100,23 @@ export const createArnsMiddleware = ({
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 (config.ARNS_RESOLVER_ENFORCE_UNDERNAME_LIMIT && 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.8":
version "2.5.0-alpha.8"
resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.5.0-alpha.8.tgz#7057896b7858122237c69f11a2bbaeb59ce811af"
integrity sha512-+cE/jsfgQEwHb7rUNLvU77Tmhien+y3O9xOR+sE5Agj+RBwJthrl3JxZjAyd3nMaJ6OweLtZWhmHqCI+RQRAAg==
dependencies:
"@dha-team/arbundles" "^1.0.1"
"@permaweb/aoconnect" "^0.0.57"
Expand Down