diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index c7db34e..6f16aa7 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -6,6 +6,12 @@ info:
description: |
AR.IO ArNS Resolver
components:
+ securitySchemes:
+ bearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: apiToken
+ description: ADMIN_API_KEY set in your .env file.
schemas:
ArweaveAddress:
type: string
@@ -67,6 +73,47 @@ paths:
application/json:
schema:
'$ref': '#/components/schemas/Info'
+ '/ar-io/resolver/admin/evaluate':
+ post:
+ security:
+ - bearerAuth: []
+ responses:
+ '200':
+ description: |-
+ 200 response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message: { type: string }
+ '202':
+ description: |-
+ 202 response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message: { type: string }
+ '401':
+ description: |-
+ 401 response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message: { type: string }
+ '500':
+ description: |-
+ 500 response
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ error: { type: string }
'/ar-io/resolver/records/{name}':
head:
parameters:
diff --git a/src/config.ts b/src/config.ts
index daf431a..ce6dce8 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -21,6 +21,8 @@ import * as env from './lib/env.js';
dotenv.config();
+export const ADMIN_API_KEY = env.varOrRandom('ADMIN_API_KEY');
+
export const EVALUATION_INTERVAL_MS = +env.varOrDefault(
'EVALUATION_INTERVAL_MS',
`${1000 * 60 * 15}`, // 15 mins by default
diff --git a/src/lib/env.ts b/src/lib/env.ts
index 7b1c3ef..6964390 100644
--- a/src/lib/env.ts
+++ b/src/lib/env.ts
@@ -15,6 +15,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
+import crypto from 'node:crypto';
+
+import log from '../log.js';
export function varOrDefault(envVarName: string, defaultValue: string): string {
const value = process.env[envVarName];
@@ -25,3 +28,13 @@ export function varOrUndefined(envVarName: string): string | undefined {
const value = process.env[envVarName];
return value !== undefined && value.trim() !== '' ? value : undefined;
}
+
+export function varOrRandom(envVarName: string): string {
+ const value = process.env[envVarName];
+ if (value === undefined) {
+ const value = crypto.randomBytes(32).toString('base64url');
+ log.info(`${envVarName} not provided, generated random value: ${value}`);
+ return value;
+ }
+ return value;
+}
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..ba8d79c
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,34 @@
+/**
+ * AR.IO ArNS Resolver
+ * Copyright (C) 2023 Permanent Data Solutions, Inc. All Rights Reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { NextFunction, Request, Response } from 'express';
+
+import * as config from './config.js';
+
+export const adminMiddleware = (
+ req: Request,
+ res: Response,
+ next: NextFunction,
+) => {
+ if (req.headers['authorization'] !== `Bearer ${config.ADMIN_API_KEY}`) {
+ res.status(401).send({
+ message: 'Unauthorized',
+ });
+ return;
+ }
+ next();
+};
diff --git a/src/server.ts b/src/server.ts
index bf6b6c6..24b2701 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -24,7 +24,13 @@ import YAML from 'yaml';
import * as config from './config.js';
import log from './log.js';
-import { cache, getLastEvaluatedTimestamp } from './system.js';
+import { adminMiddleware } from './middleware.js';
+import {
+ cache,
+ evaluateArNSNames,
+ getLastEvaluatedTimestamp,
+ isEvaluationInProgress,
+} from './system.js';
import { ArNSResolvedData } from './types.js';
// HTTP server
@@ -34,7 +40,7 @@ export const app = express();
app.use(
cors({
origin: '*',
- methods: ['GET', 'HEAD'],
+ methods: ['GET', 'HEAD', 'POST'],
}),
);
@@ -86,6 +92,23 @@ app.get('/ar-io/resolver/info', (_req, res) => {
});
});
+app.post('/ar-io/resolver/admin/evaluate', adminMiddleware, (_req, res) => {
+ // TODO: we could support post request to trigger evaluation for specific names rather than re-evaluate everything
+ if (isEvaluationInProgress()) {
+ res.status(202).send({
+ message: 'Evaluation in progress',
+ });
+ } else {
+ log.info('Evaluation triggered by request', {
+ processId: config.IO_PROCESS_ID,
+ });
+ evaluateArNSNames(); // don't await
+ res.status(200).send({
+ message: 'Evaluation triggered',
+ });
+ }
+});
+
app.head('/ar-io/resolver/records/:name', async (req, res) => {
try {
log.debug('Checking cache for record', { name: req.params.name });
diff --git a/src/system.ts b/src/system.ts
index f2421f0..03e3fa7 100644
--- a/src/system.ts
+++ b/src/system.ts
@@ -33,6 +33,7 @@ import { ArNSResolvedData } from './types.js';
let lastEvaluationTimestamp: number | undefined;
let evaluationInProgress = false;
export const getLastEvaluatedTimestamp = () => lastEvaluationTimestamp;
+export const isEvaluationInProgress = () => evaluationInProgress;
export const contract: AoIORead = IO.init({
processId: config.IO_PROCESS_ID,
});
@@ -75,7 +76,7 @@ export async function evaluateArNSNames() {
);
log.debug('Identified unique process ids assigned to records:', {
- recordCount: Object.keys(apexRecords).length,
+ apexRecordCount: Object.keys(apexRecords).length,
processCount: processIds.size,
});
@@ -120,6 +121,7 @@ export async function evaluateArNSNames() {
log.info('Retrieved unique process ids assigned to records:', {
processCount: Object.keys(processRecordMap).length,
+ apexRecordCount: Object.keys(apexRecords).length,
});
// filter out any records associated with an invalid contract
@@ -127,6 +129,8 @@ export async function evaluateArNSNames() {
([_, record]) => record.processId in processRecordMap,
);
+ let successfulEvaluationCount = 0;
+
const insertPromises = [];
// now go through all the record names and assign them to the resolved tx ids
@@ -175,10 +179,17 @@ export async function evaluateArNSNames() {
}
// use pLimit to prevent overwhelming cache
await Promise.all(
- insertPromises.map((promise) => parallelLimit(() => promise)),
+ insertPromises.map((promise) =>
+ parallelLimit(() => promise.then(() => successfulEvaluationCount++)),
+ ),
);
- log.info('Successfully evaluated arns names', {
+ log.info('Finished evaluating arns names', {
durationMs: Date.now() - startTime,
+ apexRecordCount: Object.keys(apexRecords).length,
+ evaluatedRecordCount: successfulEvaluationCount,
+ evaluatedProcessCount: Object.keys(processRecordMap).length,
+ failedProcessCount:
+ processIds.size - Object.keys(processRecordMap).length,
});
lastEvaluationTimestamp = Date.now();
} catch (err: any) {