diff --git a/docker-compose.yaml b/docker-compose.yaml
index dfb8756a..ba0c3139 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -79,6 +79,7 @@ services:
- AWS_ENDPOINT=${AWS_ENDPOINT:-}
- AWS_S3_BUCKET=${AWS_S3_BUCKET:-}
- AWS_S3_PREFIX=${AWS_S3_PREFIX:-}
+ - AR_IO_NODE_RELEASE=${AR_IO_NODE_RELEASE:-}
depends_on:
- redis
diff --git a/src/config.ts b/src/config.ts
index 77d1c8fa..42459271 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -23,6 +23,7 @@ import { isMainThread } from 'node:worker_threads';
import { createFilter } from './filters.js';
import * as env from './lib/env.js';
import log from './log.js';
+import { release } from './version.js';
dotenv.config();
@@ -154,6 +155,11 @@ export const IO_PROCESS_ID = env.varOrDefault(
'GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc',
);
+export const AR_IO_NODE_RELEASE = env.varOrDefault(
+ 'AR_IO_NODE_RELEASE',
+ release,
+);
+
//
// Header caching
//
diff --git a/src/constants.ts b/src/constants.ts
index 5f93c415..59946892 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -19,6 +19,7 @@
const headerNames = {
hops: 'X-AR-IO-Hops',
origin: 'X-AR-IO-Origin',
+ originNodeRelease: 'X-AR-IO-Origin-Node-Release',
cache: 'X-Cache',
arnsTtlSeconds: 'X-ArNS-TTL-Seconds',
arnsResolvedId: 'X-ArNS-Resolved-Id',
diff --git a/src/data/ar-io-data-source.test.ts b/src/data/ar-io-data-source.test.ts
index 3c94da55..8f29897d 100644
--- a/src/data/ar-io-data-source.test.ts
+++ b/src/data/ar-io-data-source.test.ts
@@ -101,7 +101,11 @@ describe('ArIODataSource', () => {
verified: false,
sourceContentType: 'application/octet-stream',
cached: false,
- requestAttributes: { hops: 1, origin: undefined },
+ requestAttributes: {
+ hops: 1,
+ origin: undefined,
+ originNodeRelease: undefined,
+ },
});
});
@@ -130,14 +134,18 @@ describe('ArIODataSource', () => {
verified: false,
sourceContentType: 'application/octet-stream',
cached: false,
- requestAttributes: { hops: 1, origin: undefined },
+ requestAttributes: {
+ hops: 1,
+ origin: undefined,
+ originNodeRelease: undefined,
+ },
});
});
it('should increment hops and origin if requestAttributes are provided', async () => {
const data = await dataSource.getData({
id: 'dataId',
- requestAttributes: { origin: 'node-url', hops: 2 },
+ requestAttributes: { hops: 2 },
});
assert.deepEqual(data, {
@@ -146,7 +154,11 @@ describe('ArIODataSource', () => {
verified: false,
sourceContentType: 'application/octet-stream',
cached: false,
- requestAttributes: { hops: 3, origin: 'node-url' },
+ requestAttributes: {
+ hops: 3,
+ origin: undefined,
+ originNodeRelease: undefined,
+ },
});
});
diff --git a/src/data/ar-io-data-source.ts b/src/data/ar-io-data-source.ts
index 8f56f6af..da51a07c 100644
--- a/src/data/ar-io-data-source.ts
+++ b/src/data/ar-io-data-source.ts
@@ -25,7 +25,10 @@ import {
ContiguousDataSource,
RequestAttributes,
} from '../types.js';
-import { headerNames } from '../constants.js';
+import {
+ generateRequestAttributes,
+ parseRequestAttributesHeaders,
+} from '../lib/request-attributes.js';
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000; // 10 seconds
const DEFAULT_UPDATE_PEERS_REFRESH_INTERVAL_MS = 3_600_000; // 1 hour
@@ -169,27 +172,22 @@ export class ArIODataSource implements ContiguousDataSource {
let selectedPeer = this.selectPeer();
- const requestOriginAndHopsHeaders: { [key: string]: string } = {};
- let hops = 1;
- let origin: string | undefined;
-
- if (requestAttributes !== undefined) {
- hops = requestAttributes.hops + 1;
- requestOriginAndHopsHeaders[headerNames.hops] = hops.toString();
- if (requestAttributes.origin !== undefined) {
- origin = requestAttributes.origin;
- requestOriginAndHopsHeaders[headerNames.origin] = origin;
- }
- }
+ const requestAttributesHeaders =
+ generateRequestAttributes(requestAttributes);
try {
const response = await this.request({
peerAddress: selectedPeer,
id,
- headers: requestOriginAndHopsHeaders,
+ headers: requestAttributesHeaders?.headers || {},
});
- return this.parseResponse(response, hops, origin);
+ const parsedRequestAttributes = parseRequestAttributesHeaders({
+ headers: response.headers as { [key: string]: string },
+ currentHops: requestAttributesHeaders?.attributes.hops,
+ });
+
+ return this.parseResponse(response, parsedRequestAttributes);
} catch (error: any) {
log.error('Failed to fetch contiguous data from first random ArIO peer', {
message: error.message,
@@ -201,10 +199,15 @@ export class ArIODataSource implements ContiguousDataSource {
const response = await this.request({
peerAddress: selectedPeer,
id,
- headers: requestOriginAndHopsHeaders,
+ headers: requestAttributesHeaders?.headers || {},
});
- return this.parseResponse(response, hops, origin);
+ const parsedRequestAttributes = parseRequestAttributesHeaders({
+ headers: response.headers as { [key: string]: string },
+ currentHops: requestAttributesHeaders?.attributes.hops,
+ });
+
+ return this.parseResponse(response, parsedRequestAttributes);
} catch (error: any) {
log.error(
'Failed to fetch contiguous data from second random ArIO peer',
@@ -220,8 +223,7 @@ export class ArIODataSource implements ContiguousDataSource {
private parseResponse(
response: AxiosResponse,
- hops: number,
- origin?: string,
+ requestAttributes: RequestAttributes,
): ContiguousData {
return {
stream: response.data,
@@ -229,13 +231,7 @@ export class ArIODataSource implements ContiguousDataSource {
verified: false,
sourceContentType: response.headers['content-type'],
cached: false,
- requestAttributes: {
- hops:
- response.headers[headerNames.hops.toLowerCase()] !== undefined
- ? parseInt(response.headers[headerNames.hops.toLowerCase()])
- : hops,
- origin,
- },
+ requestAttributes,
};
}
}
diff --git a/src/data/gateway-data-source.test.ts b/src/data/gateway-data-source.test.ts
index 0d63a569..25df9d05 100644
--- a/src/data/gateway-data-source.test.ts
+++ b/src/data/gateway-data-source.test.ts
@@ -39,6 +39,7 @@ beforeEach(async () => {
headers: {
'content-length': '123',
'content-type': 'application/json',
+ 'X-AR-IO-Origin': 'node-url',
},
}),
defaults: {
@@ -77,6 +78,7 @@ describe('GatewayDataSource', () => {
requestAttributes: {
hops: requestAttributes.hops + 1,
origin: requestAttributes.origin,
+ originNodeRelease: undefined,
},
});
});
@@ -173,7 +175,7 @@ describe('GatewayDataSource', () => {
});
assert.equal(data.requestAttributes?.hops, 1);
- assert.equal(data.requestAttributes?.origin, undefined);
+ assert.equal(data.requestAttributes?.origin, 'node-url');
});
it('should increment hops in the response', async () => {
@@ -183,7 +185,7 @@ describe('GatewayDataSource', () => {
});
assert.equal(data.requestAttributes?.hops, 6);
- assert.equal(data.requestAttributes?.origin, undefined);
+ assert.equal(data.requestAttributes?.origin, 'node-url');
});
});
});
diff --git a/src/data/gateway-data-source.ts b/src/data/gateway-data-source.ts
index 44ef7b00..b4b02045 100644
--- a/src/data/gateway-data-source.ts
+++ b/src/data/gateway-data-source.ts
@@ -17,7 +17,10 @@
*/
import { default as axios } from 'axios';
import winston from 'winston';
-import { headerNames } from '../constants.js';
+import {
+ generateRequestAttributes,
+ parseRequestAttributesHeaders,
+} from '../lib/request-attributes.js';
import {
ContiguousData,
@@ -61,29 +64,23 @@ export class GatewayDataSource implements ContiguousDataSource {
path,
});
- const requestOriginAndHopsHeaders: { [key: string]: string } = {};
- let hops;
- let origin;
- if (requestAttributes !== undefined) {
- hops = requestAttributes.hops + 1;
- requestOriginAndHopsHeaders[headerNames.hops] = hops.toString();
-
- if (requestAttributes.origin !== undefined) {
- origin = requestAttributes.origin;
- requestOriginAndHopsHeaders[headerNames.origin] = origin;
- }
- } else {
- hops = 1;
- }
+ const requestAttributesHeaders =
+ generateRequestAttributes(requestAttributes);
const response = await this.trustedGatewayAxios.request({
method: 'GET',
headers: {
'Accept-Encoding': 'identity',
- ...requestOriginAndHopsHeaders,
+ ...requestAttributesHeaders?.headers,
},
url: path,
responseType: 'stream',
+ params: {
+ 'ar-io-hops': requestAttributesHeaders?.attributes.hops,
+ 'ar-io-origin': requestAttributesHeaders?.attributes.origin,
+ 'ar-io-origin-release':
+ requestAttributesHeaders?.attributes.originNodeRelease,
+ },
});
if (response.status !== 200) {
@@ -98,13 +95,10 @@ export class GatewayDataSource implements ContiguousDataSource {
verified: false,
sourceContentType: response.headers['content-type'],
cached: false,
- requestAttributes: {
- hops:
- response.headers[headerNames.hops.toLowerCase()] !== undefined
- ? parseInt(response.headers[headerNames.hops.toLowerCase()])
- : hops,
- origin,
- },
+ requestAttributes: parseRequestAttributesHeaders({
+ headers: response.headers as { [key: string]: string },
+ currentHops: requestAttributesHeaders?.attributes.hops,
+ }),
};
}
}
diff --git a/src/data/s3-data-source.ts b/src/data/s3-data-source.ts
index e7c0926a..c1fabfbf 100644
--- a/src/data/s3-data-source.ts
+++ b/src/data/s3-data-source.ts
@@ -16,7 +16,6 @@
* along with this program. If not, see .
*/
import winston from 'winston';
-import { headerNames } from '../constants.js';
import {
ContiguousData,
@@ -26,6 +25,7 @@ import {
import { AwsLiteS3 } from '@aws-lite/s3-types';
import { Readable } from 'node:stream';
import { AwsLiteClient } from '@aws-lite/client';
+import { generateRequestAttributes } from '../lib/request-attributes.js';
export class S3DataSource implements ContiguousDataSource {
private log: winston.Logger;
@@ -121,20 +121,8 @@ export class S3DataSource implements ContiguousDataSource {
},
});
- const requestOriginAndHopsHeaders: { [key: string]: string } = {};
- let hops;
- let origin;
- if (requestAttributes !== undefined) {
- hops = requestAttributes.hops + 1;
- requestOriginAndHopsHeaders[headerNames.hops] = hops.toString();
-
- if (requestAttributes.origin !== undefined) {
- origin = requestAttributes.origin;
- requestOriginAndHopsHeaders[headerNames.origin] = origin;
- }
- } else {
- hops = 1;
- }
+ const requestAttributesHeaders =
+ generateRequestAttributes(requestAttributes);
if (response.ContentLength === undefined) {
throw new Error('Content-Length header missing from S3 response');
@@ -150,10 +138,7 @@ export class S3DataSource implements ContiguousDataSource {
verified: false,
sourceContentType,
cached: false,
- requestAttributes: {
- hops,
- origin,
- },
+ requestAttributes: requestAttributesHeaders?.attributes,
};
} catch (error: any) {
log.error('Failed to fetch contiguous data from S3', {
diff --git a/src/data/tx-chunks-data-source.ts b/src/data/tx-chunks-data-source.ts
index c137c1fe..5dfacc39 100644
--- a/src/data/tx-chunks-data-source.ts
+++ b/src/data/tx-chunks-data-source.ts
@@ -17,6 +17,7 @@
*/
import { Readable } from 'node:stream';
import winston from 'winston';
+import { generateRequestAttributes } from '../lib/request-attributes.js';
import {
ChainSource,
@@ -115,13 +116,8 @@ export class TxChunksDataSource implements ContiguousDataSource {
size,
verified: true,
cached: false,
- requestAttributes: {
- hops:
- requestAttributes?.hops !== undefined
- ? requestAttributes.hops + 1
- : 1,
- origin: requestAttributes?.origin,
- },
+ requestAttributes:
+ generateRequestAttributes(requestAttributes)?.attributes,
};
}
}
diff --git a/src/lib/request-attributes.test.ts b/src/lib/request-attributes.test.ts
new file mode 100644
index 00000000..72da0443
--- /dev/null
+++ b/src/lib/request-attributes.test.ts
@@ -0,0 +1,145 @@
+/**
+ * AR.IO Gateway
+ * Copyright (C) 2022-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 { strict as assert } from 'node:assert';
+import { describe, it } from 'node:test';
+import { headerNames } from '../constants.js';
+import { RequestAttributes } from '../types.js';
+import {
+ generateRequestAttributes,
+ parseRequestAttributesHeaders,
+} from './request-attributes.js';
+
+describe('Request attributes functions', () => {
+ describe('generateRequestAttributes', () => {
+ it('should returns undefined when input is undefined', () => {
+ const result = generateRequestAttributes(undefined);
+ assert.strictEqual(result, undefined);
+ });
+
+ it('should handles hops, origin, and nodeRelease correctly', () => {
+ const input: RequestAttributes = {
+ hops: 2,
+ origin: 'test-origin',
+ originNodeRelease: 'v1.0.0',
+ };
+ const expected = {
+ headers: {
+ [headerNames.hops]: '3',
+ [headerNames.origin]: 'test-origin',
+ [headerNames.originNodeRelease]: 'v1.0.0',
+ },
+ attributes: {
+ hops: 3,
+ origin: 'test-origin',
+ originNodeRelease: 'v1.0.0',
+ },
+ };
+
+ const result = generateRequestAttributes(input);
+ assert.deepStrictEqual(result, expected);
+ });
+
+ it('should handles missing hops correctly', () => {
+ const input = {
+ origin: 'test-origin',
+ originNodeRelease: 'v1.0.0',
+ } as RequestAttributes;
+ const expected = {
+ headers: {
+ [headerNames.hops]: '1',
+ [headerNames.origin]: 'test-origin',
+ [headerNames.originNodeRelease]: 'v1.0.0',
+ },
+ attributes: {
+ hops: 1,
+ origin: 'test-origin',
+ originNodeRelease: 'v1.0.0',
+ },
+ };
+
+ const result = generateRequestAttributes(input);
+ assert.deepStrictEqual(result, expected);
+ });
+
+ it('should handles missing origin and nodeVersion correctly', () => {
+ const input: RequestAttributes = {
+ hops: 2,
+ };
+ const expected = {
+ headers: {
+ [headerNames.hops]: '3',
+ },
+ attributes: {
+ hops: 3,
+ },
+ };
+
+ const result = generateRequestAttributes(input);
+ assert.deepStrictEqual(result, expected);
+ });
+ });
+
+ describe('parseRequestAttributesHeaders', () => {
+ it('should parses headers correctly', () => {
+ const headers = {
+ [headerNames.hops]: '3',
+ [headerNames.origin]: 'test-origin',
+ [headerNames.originNodeRelease]: 'v1.0.0',
+ };
+ const expected: RequestAttributes = {
+ hops: 3,
+ origin: headers[headerNames.origin],
+ originNodeRelease: headers[headerNames.originNodeRelease],
+ };
+
+ const result = parseRequestAttributesHeaders({ headers });
+ assert.deepStrictEqual(result, expected);
+ });
+
+ it('should handles missing hops and currentHops', () => {
+ const headers = {
+ [headerNames.origin]: 'test-origin',
+ [headerNames.originNodeRelease]: 'v1.0.0',
+ };
+ const expected: RequestAttributes = {
+ hops: 1,
+ origin: headers[headerNames.origin],
+ originNodeRelease: headers[headerNames.originNodeRelease],
+ };
+
+ const result = parseRequestAttributesHeaders({ headers });
+ assert.deepStrictEqual(result, expected);
+ });
+
+ it('should use currentHops when hops header is missing', () => {
+ const headers = {
+ [headerNames.origin]: 'test-origin',
+ [headerNames.originNodeRelease]: 'v1.0.0',
+ };
+ const currentHops = 2;
+ const expected: RequestAttributes = {
+ hops: currentHops,
+ origin: headers[headerNames.origin],
+ originNodeRelease: headers[headerNames.originNodeRelease],
+ };
+
+ const result = parseRequestAttributesHeaders({ headers, currentHops });
+ assert.deepStrictEqual(result, expected);
+ });
+ });
+});
diff --git a/src/lib/request-attributes.ts b/src/lib/request-attributes.ts
new file mode 100644
index 00000000..3e16e541
--- /dev/null
+++ b/src/lib/request-attributes.ts
@@ -0,0 +1,87 @@
+/**
+ * AR.IO Gateway
+ * Copyright (C) 2022-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 { headerNames } from '../constants.js';
+import { RequestAttributes } from '../types.js';
+
+export const generateRequestAttributes = (
+ requestAttributes: RequestAttributes | undefined,
+):
+ | { headers: Record; attributes: RequestAttributes }
+ | undefined => {
+ if (requestAttributes === undefined) {
+ return undefined;
+ }
+
+ const headers: { [key: string]: string } = {};
+ const attributes = {} as RequestAttributes;
+
+ if (requestAttributes.hops !== undefined) {
+ const hops = requestAttributes.hops + 1;
+ headers[headerNames.hops] = hops.toString();
+ attributes.hops = hops;
+ } else {
+ headers[headerNames.hops] = '1';
+ attributes.hops = 1;
+ }
+
+ if (requestAttributes.origin !== undefined) {
+ headers[headerNames.origin] = requestAttributes.origin;
+ attributes.origin = requestAttributes.origin;
+ }
+
+ if (requestAttributes.originNodeRelease !== undefined) {
+ headers[headerNames.originNodeRelease] =
+ requestAttributes.originNodeRelease;
+ attributes.originNodeRelease = requestAttributes.originNodeRelease;
+ }
+
+ return { headers, attributes };
+};
+
+export const parseRequestAttributesHeaders = ({
+ headers,
+ currentHops,
+}: {
+ headers: Record;
+ currentHops?: number;
+}): RequestAttributes => {
+ const headersLowercaseKeys = Object.keys(headers).reduce(
+ (newHeaders, key) => {
+ newHeaders[key.toLowerCase()] = headers[key];
+ return newHeaders;
+ },
+ {} as Record,
+ );
+
+ let hops;
+ if (headersLowercaseKeys[headerNames.hops.toLowerCase()] !== undefined) {
+ hops = parseInt(headersLowercaseKeys[headerNames.hops.toLowerCase()], 10);
+ } else if (currentHops !== undefined) {
+ hops = currentHops;
+ } else {
+ hops = 1;
+ }
+
+ return {
+ hops,
+ origin: headersLowercaseKeys[headerNames.origin.toLowerCase()],
+ originNodeRelease:
+ headersLowercaseKeys[headerNames.originNodeRelease.toLowerCase()],
+ };
+};
diff --git a/src/routes/data/handlers.ts b/src/routes/data/handlers.ts
index f6c7415f..7ef06feb 100644
--- a/src/routes/data/handlers.ts
+++ b/src/routes/data/handlers.ts
@@ -82,6 +82,12 @@ const setDataHeaders = ({
if (data.requestAttributes.origin !== undefined) {
res.header(headerNames.origin, data.requestAttributes.origin);
}
+ if (data.requestAttributes.originNodeRelease !== undefined) {
+ res.header(
+ headerNames.originNodeRelease,
+ data.requestAttributes.originNodeRelease,
+ );
+ }
}
// Use the content type from the L1 or data item index if available
@@ -92,20 +98,16 @@ const setDataHeaders = ({
);
};
-const getRequestAttributes = (
- req: Request,
- arnsRootHost?: string,
-): RequestAttributes => {
- let origin: string | undefined;
- const originHeader = req.headers[headerNames.origin.toLowerCase()] as string;
+const getRequestAttributes = (req: Request): RequestAttributes => {
const hopsHeader = req.headers[headerNames.hops.toLowerCase()] as string;
- if (originHeader !== undefined) {
- origin = originHeader;
- } else if (arnsRootHost !== undefined) {
- origin = arnsRootHost;
- }
const hops = parseInt(hopsHeader) || 0;
- return { origin, hops };
+ return {
+ hops,
+ origin: req.headers[headerNames.origin.toLowerCase()] as string | undefined,
+ originNodeRelease: req.headers[
+ headerNames.originNodeRelease.toLowerCase()
+ ] as string | undefined,
+ };
};
const handleRangeRequest = (
@@ -226,16 +228,14 @@ export const createRawDataHandler = ({
dataIndex,
dataSource,
blockListValidator,
- arnsRootHost,
}: {
log: Logger;
dataSource: ContiguousDataSource;
dataIndex: ContiguousDataIndex;
blockListValidator: BlockListValidator;
- arnsRootHost?: string;
}) => {
return asyncHandler(async (req: Request, res: Response) => {
- const requestAttributes = getRequestAttributes(req, arnsRootHost);
+ const requestAttributes = getRequestAttributes(req);
const id = req.params[0];
// Return 404 if the data is blocked by ID
@@ -289,6 +289,7 @@ export const createRawDataHandler = ({
dataAttributes,
requestAttributes,
});
+
// Check if the request includes a Range header
const rangeHeader = req.headers.range;
if (rangeHeader !== undefined) {
@@ -445,17 +446,15 @@ export const createDataHandler = ({
dataSource,
blockListValidator,
manifestPathResolver,
- arnsRootHost,
}: {
log: Logger;
dataSource: ContiguousDataSource;
dataIndex: ContiguousDataIndex;
blockListValidator: BlockListValidator;
manifestPathResolver: ManifestPathResolver;
- arnsRootHost?: string;
}) => {
return asyncHandler(async (req: Request, res: Response) => {
- const requestAttributes = getRequestAttributes(req, arnsRootHost);
+ const requestAttributes = getRequestAttributes(req);
const arnsResolvedId = res.getHeader(headerNames.arnsResolvedId);
let id: string | undefined;
let manifestPath: string | undefined;
diff --git a/src/routes/data/index.ts b/src/routes/data/index.ts
index d7dedd2f..0c9c037a 100644
--- a/src/routes/data/index.ts
+++ b/src/routes/data/index.ts
@@ -20,7 +20,6 @@ import { Router } from 'express';
import log from '../../log.js';
import * as system from '../../system.js';
import { createDataHandler, createRawDataHandler } from './handlers.js';
-import * as config from '../../config.js';
const DATA_PATH_REGEX =
/^\/?([a-zA-Z0-9-_]{43})\/?$|^\/?([a-zA-Z0-9-_]{43})\/(.*)$/i;
@@ -35,7 +34,6 @@ export const dataHandler = createDataHandler({
dataSource: system.contiguousDataSource,
blockListValidator: system.blockListValidator,
manifestPathResolver: system.manifestPathResolver,
- arnsRootHost: config.ARNS_ROOT_HOST,
});
export const dataRouter = Router();
@@ -47,7 +45,6 @@ dataRouter.get(
dataIndex: system.contiguousDataIndex,
dataSource: system.contiguousDataSource,
blockListValidator: system.blockListValidator,
- arnsRootHost: config.ARNS_ROOT_HOST,
}),
);
dataRouter.get(FARCASTER_FRAME_DATA_PATH_REGEX, dataHandler);
diff --git a/src/types.d.ts b/src/types.d.ts
index 4e6499e7..1370648c 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -324,6 +324,7 @@ interface GqlBlocksResult {
interface RequestAttributes {
hops: number;
origin?: string;
+ originNodeRelease?: string;
}
export interface GqlQueryable {
diff --git a/test/end-to-end/data.test.ts b/test/end-to-end/data.test.ts
index 09fb5d6f..f1935e1a 100644
--- a/test/end-to-end/data.test.ts
+++ b/test/end-to-end/data.test.ts
@@ -277,7 +277,7 @@ describe('X-Cache header', function () {
});
});
-describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
+describe('X-AR-IO headers', function () {
describe('with ARNS_ROOT_HOST', function () {
let compose: StartedDockerComposeEnvironment;
@@ -306,23 +306,26 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
const res = await axios.get(`http://localhost:4000/raw/${tx3}`);
assert.equal(res.headers['x-ar-io-hops'], '1');
- assert.equal(res.headers['x-ar-io-origin'], 'ar-io.localhost');
+ assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
const resWithHeaders = await axios.get(
`http://localhost:4000/raw/${tx3}`,
{
headers: {
'X-AR-IO-Hops': '5',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': 'v1.0.0',
},
},
);
assert.equal(resWithHeaders.headers['x-ar-io-hops'], '6');
- assert.equal(resWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(resWithHeaders.headers['x-ar-io-origin'], undefined);
+ assert.equal(resWithHeaders.headers['x-ar-io-node-release'], undefined);
});
- it('Verifying that / for a manifest with a missing index returns no hops and origin', async function () {
+ it('Verifying that / for a manifest with a missing index returns no hops, origin and node release', async function () {
const res = await axios.get(`http://localhost:4000/${tx1}`, {
validateStatus: () => true,
headers: {
@@ -332,9 +335,10 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
assert.equal(res.headers['x-ar-io-hops'], undefined);
assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
});
- it('verifying that / for a manifest with a valid index returns hops and origin', async function () {
+ it('verifying that / for a manifest with a valid index returns hops, origin and node release', async function () {
const res = await axios.get(`http://localhost:4000/${tx2}/`, {
headers: {
Host: 'zhtq6zlaiu54cz5ss7vqxlf7yquechmhzcpmwccmrcu7w44f4zbq.ar-io.localhost',
@@ -342,21 +346,24 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
});
assert.equal(res.headers['x-ar-io-hops'], '1');
- assert.equal(res.headers['x-ar-io-origin'], 'ar-io.localhost');
+ assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
const resWithHeaders = await axios.get(`http://localhost:4000/${tx2}/`, {
headers: {
Host: 'zhtq6zlaiu54cz5ss7vqxlf7yquechmhzcpmwccmrcu7w44f4zbq.ar-io.localhost',
'X-AR-IO-Hops': '2',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': 'v2.0.0',
},
});
assert.equal(resWithHeaders.headers['x-ar-io-hops'], '3');
assert.equal(resWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(resWithHeaders.headers['x-ar-io-node-release'], undefined);
});
- it('Verifying that / for a non-manifest returns hops and origin', async function () {
+ it('Verifying that / for a non-manifest returns hops, origin and node release', async function () {
const res = await axios.get(`http://localhost:4000/${tx3}`, {
headers: {
Host: 'sw3yqmkl5ajki5vl5jflcpqy43opvgtpngs6tel3eltuhq73l2jq.ar-io.localhost',
@@ -364,18 +371,21 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
});
assert.equal(res.headers['x-ar-io-hops'], '1');
- assert.equal(res.headers['x-ar-io-origin'], 'ar-io.localhost');
+ assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
const resWithHeaders = await axios.get(`http://localhost:4000/${tx3}`, {
headers: {
'X-AR-IO-Hops': '5',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': 'v2.0.0',
Host: 'sw3yqmkl5ajki5vl5jflcpqy43opvgtpngs6tel3eltuhq73l2jq.ar-io.localhost',
},
});
assert.equal(resWithHeaders.headers['x-ar-io-hops'], '6');
assert.equal(resWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(resWithHeaders.headers['x-ar-io-node-release'], undefined);
});
});
@@ -407,31 +417,35 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
assert.equal(res.headers['x-ar-io-hops'], '1');
assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
const resWithHeaders = await axios.get(
`http://localhost:4000/raw/${tx3}`,
{
headers: {
'X-AR-IO-Hops': '5',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': '10',
},
},
);
assert.equal(resWithHeaders.headers['x-ar-io-hops'], '6');
- assert.equal(resWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(resWithHeaders.headers['x-ar-io-origin'], undefined);
+ assert.equal(resWithHeaders.headers['x-ar-io-node-release'], undefined);
});
- it('Verifying that / for a manifest with a missing index returns no hops and origin', async function () {
+ it('Verifying that / for a manifest with a missing index returns no hops, origin and node release', async function () {
const res = await axios.get(`http://localhost:4000/${tx1}`, {
validateStatus: () => true,
});
assert.equal(res.headers['x-ar-io-hops'], undefined);
assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
});
- it('verifying that / for a manifest with a valid index returns hops and origin', async function () {
+ it('verifying that / for a manifest with a valid index returns hops, origin and node release', async function () {
const res = await axios.get(`http://localhost:4000/${tx2}/`, {
headers: {
Host: 'zhtq6zlaiu54cz5ss7vqxlf7yquechmhzcpmwccmrcu7w44f4zbq.ar-io.localhost',
@@ -440,20 +454,23 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
assert.equal(res.headers['x-ar-io-hops'], '1');
assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
const resWithHeaders = await axios.get(`http://localhost:4000/${tx2}/`, {
headers: {
Host: 'zhtq6zlaiu54cz5ss7vqxlf7yquechmhzcpmwccmrcu7w44f4zbq.ar-io.localhost',
'X-AR-IO-Hops': '2',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': '10',
},
});
assert.equal(resWithHeaders.headers['x-ar-io-hops'], '3');
assert.equal(resWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(resWithHeaders.headers['x-ar-io-node-release'], undefined);
});
- it('Verifying that / for a non-manifest returns hops and origin', async function () {
+ it('Verifying that / for a non-manifest returns hops, origin and node release', async function () {
const res = await axios.get(`http://localhost:4000/${tx3}`, {
headers: {
Host: 'sw3yqmkl5ajki5vl5jflcpqy43opvgtpngs6tel3eltuhq73l2jq.ar-io.localhost',
@@ -462,17 +479,20 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
assert.equal(res.headers['x-ar-io-hops'], '1');
assert.equal(res.headers['x-ar-io-origin'], undefined);
+ assert.equal(res.headers['x-ar-io-node-release'], undefined);
const resWithHeaders = await axios.get(`http://localhost:4000/${tx3}`, {
headers: {
'X-AR-IO-Hops': '5',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': '10',
Host: 'sw3yqmkl5ajki5vl5jflcpqy43opvgtpngs6tel3eltuhq73l2jq.ar-io.localhost',
},
});
assert.equal(resWithHeaders.headers['x-ar-io-hops'], '6');
assert.equal(resWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(resWithHeaders.headers['x-ar-io-node-release'], undefined);
});
});
@@ -491,6 +511,7 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
'Content-Length': '11',
'X-AR-IO-Hops': hops ? (parseInt(hops) + 1).toString() : '1',
'X-AR-IO-Origin': origin ?? 'fake-gateway',
+ 'X-AR-IO-Node-Release': '10',
});
res.end('hello world');
});
@@ -532,20 +553,23 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
const req = await axios.get(`http://localhost:${corePort}/raw/${tx2}`);
assert.equal(req.headers['x-ar-io-hops'], '2');
- assert.equal(req.headers['x-ar-io-origin'], 'ar-io.localhost');
+ assert.equal(req.headers['x-ar-io-origin'], 'fake-gateway');
+ assert.equal(req.headers['x-ar-io-node-release'], '10');
const reqWithHeaders = await axios.get(
`http://localhost:${corePort}/raw/${tx3}`,
{
headers: {
'X-AR-IO-Hops': '5',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': '20',
},
},
);
assert.equal(reqWithHeaders.headers['x-ar-io-hops'], '7');
assert.equal(reqWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(reqWithHeaders.headers['x-ar-io-node-release'], '10');
});
it('Verifying that / returns expected response', async function () {
@@ -555,7 +579,8 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
},
});
assert.equal(req.headers['x-ar-io-hops'], '2');
- assert.equal(req.headers['x-ar-io-origin'], 'ar-io.localhost');
+ assert.equal(req.headers['x-ar-io-origin'], 'fake-gateway');
+ assert.equal(req.headers['x-ar-io-node-release'], '10');
const reqWithHeaders = await axios.get(
`http://localhost:${corePort}/${tx3}`,
@@ -563,13 +588,15 @@ describe('X-AR-IO-Hops and X-Ar-IO-Origin headers', function () {
headers: {
Host: 'sw3yqmkl5ajki5vl5jflcpqy43opvgtpngs6tel3eltuhq73l2jq.ar-io.localhost',
'X-AR-IO-Hops': '10',
- 'X-Ar-IO-Origin': 'another-host',
+ 'X-AR-IO-Origin': 'another-host',
+ 'X-AR-IO-Node-Release': '20',
},
},
);
assert.equal(reqWithHeaders.headers['x-ar-io-hops'], '12');
assert.equal(reqWithHeaders.headers['x-ar-io-origin'], 'another-host');
+ assert.equal(reqWithHeaders.headers['x-ar-io-node-release'], '10');
});
});
});