From f094f153f23e193a9b6c6e09ea37f5d50e7375d2 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Thu, 29 Feb 2024 12:44:43 -0600 Subject: [PATCH 01/18] chore(tests): add tests for blockheight and sortkey, break tests into domain centric files --- src/common/caches/arns-remote-cache.ts | 47 ++++++++++---- src/common/http.ts | 3 + src/constants.ts | 4 ++ src/types/common.ts | 27 ++++++-- src/utils/index.ts | 1 + src/utils/smartweave/evaluation.test.ts | 13 ++++ src/utils/smartweave/evaluation.ts | 49 ++++++++++++++ src/utils/smartweave/index.ts | 17 +++++ tests/arns-remote-cache.test.ts | 57 ----------------- tests/arns-remote-cache/balances.test.ts | 76 ++++++++++++++++++++++ tests/arns-remote-cache/gateways.test.ts | 72 +++++++++++++++++++++ tests/arns-remote-cache/records.test.ts | 81 ++++++++++++++++++++++++ 12 files changed, 374 insertions(+), 73 deletions(-) create mode 100644 src/utils/smartweave/evaluation.test.ts create mode 100644 src/utils/smartweave/evaluation.ts create mode 100644 src/utils/smartweave/index.ts delete mode 100644 tests/arns-remote-cache.test.ts create mode 100644 tests/arns-remote-cache/balances.test.ts create mode 100644 tests/arns-remote-cache/gateways.test.ts create mode 100644 tests/arns-remote-cache/records.test.ts diff --git a/src/common/caches/arns-remote-cache.ts b/src/common/caches/arns-remote-cache.ts index f74fe625..ab83394e 100644 --- a/src/common/caches/arns-remote-cache.ts +++ b/src/common/caches/arns-remote-cache.ts @@ -21,6 +21,7 @@ import { ArNSStateResponse, Gateway, HTTPClient, + ReadInteractionFilters, } from '../../types/index.js'; import { NotFound } from '../error.js'; import { AxiosHTTPService } from '../http.js'; @@ -58,32 +59,44 @@ export class ArNSRemoteCache implements ArIOContract { } } - async getGateway({ address }: { address: string }) { + async getGateway({ + address, + blockHeight, + sortKey, + }: { address: string } & ReadInteractionFilters) { this.logger.debug(`Fetching gateway ${address}`); - const gateway = await this.getGateways().then((gateways) => { - if (gateways[address] === undefined) { - throw new NotFound(`Gateway not found: ${address}`); - } - return gateways[address]; - }); + const gateway = await this.getGateways({ blockHeight, sortKey }).then( + (gateways) => { + if (gateways[address] === undefined) { + throw new NotFound(`Gateway not found: ${address}`); + } + return gateways[address]; + }, + ); return gateway; } - async getGateways() { + async getGateways({ blockHeight, sortKey }: ReadInteractionFilters = {}) { this.logger.debug(`Fetching gateways`); const { result } = await this.http.get< ArNSStateResponse<'result', Record> >({ endpoint: `/contract/${this.contractTxId.toString()}/read/gateways`, + params: { blockHeight, sortKey: sortKey?.toString() }, }); return result; } - async getBalance({ address }: { address: string }) { + async getBalance({ + address, + blockHeight, + sortKey, + }: { address: string } & ReadInteractionFilters) { this.logger.debug(`Fetching balance for ${address}`); const { result } = await this.http .get>({ endpoint: `/contract/${this.contractTxId.toString()}/state/balances/${address}`, + params: { blockHeight, sortKey: sortKey?.toString() }, }) .catch((e) => { if (e instanceof NotFound) { @@ -94,32 +107,42 @@ export class ArNSRemoteCache implements ArIOContract { return result; } - async getBalances() { + async getBalances({ blockHeight, sortKey }: ReadInteractionFilters = {}) { this.logger.debug(`Fetching balances`); const { result } = await this.http.get< ArNSStateResponse<'result', Record> >({ endpoint: `/contract/${this.contractTxId.toString()}/state/balances`, + params: { blockHeight, sortKey: sortKey?.toString() }, }); return result; } - async getRecord({ domain }: { domain: string }): Promise { + async getRecord({ + domain, + blockHeight, + sortKey, + }: { domain: string } & ReadInteractionFilters): Promise { this.logger.debug(`Fetching record for ${domain}`); const { result } = await this.http.get< ArNSStateResponse<'result', ArNSNameData> >({ endpoint: `/contract/${this.contractTxId.toString()}/state/records/${domain}`, + params: { blockHeight, sortKey: sortKey?.toString() }, }); return result; } - async getRecords(): Promise> { + async getRecords({ + blockHeight, + sortKey, + }: ReadInteractionFilters = {}): Promise> { this.logger.debug(`Fetching all records`); const { result } = await this.http.get< ArNSStateResponse<'result', Record> >({ endpoint: `/contract/${this.contractTxId.toString()}/state/records`, + params: { blockHeight, sortKey: sortKey?.toString() }, }); return result; } diff --git a/src/common/http.ts b/src/common/http.ts index d9751152..ebbf4b6c 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -39,16 +39,19 @@ export class AxiosHTTPService implements HTTPClient { signal, allowedStatuses = [200, 202], headers, + params, }: { endpoint: string; signal?: AbortSignal; allowedStatuses?: number[]; headers?: Record; + params?: Record; // eslint-disable-line @typescript-eslint/no-explicit-any }): Promise { this.logger.debug(`Get request to endpoint: ${endpoint}`); const { status, statusText, data } = await this.axios.get(endpoint, { headers, signal, + params, }); if (!allowedStatuses.includes(status)) { diff --git a/src/constants.ts b/src/constants.ts index 3e49f6c0..54bfdf75 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,10 @@ */ export const ARWEAVE_TX_REGEX = new RegExp('^[a-zA-Z0-9_-]{43}$'); +// sortkey: padded blockheight to 12, JS timestamp, hash of transactionID + block hash. Timestamp only applicable to L2 and normally is all zeros. +export const SORT_KEY_REGEX = new RegExp( + '^[0-9]{12},[0-9]{13},[a-fA-F0-9]{64}$', +); export const ARNS_TESTNET_REGISTRY_TX = process.env.ARNS_REGISTRY_TX ?? 'bLAgYxAdX2Ry-nt6aH2ixgvJXbpsEYm28NgJgyqfs-U'; diff --git a/src/types/common.ts b/src/types/common.ts index 76ec92b2..7afd0c64 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -14,15 +14,32 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { SmartWeaveSortKey } from '../utils/index.js'; import { ArNSNameData, Gateway } from './contract-state.js'; +export type EvaluationFilters = { + blockHeight?: number; + sortKey?: SmartWeaveSortKey; // should be tested against regex for validity +}; + +// TODO: extend type with other read filters (e.g max eval time) +export type ReadInteractionFilters = EvaluationFilters; + // TODO: extend with additional methods export interface ArIOContract { - getGateway({ address }: { address: WalletAddress }): Promise; + getGateway( + props: { address: WalletAddress } & ReadInteractionFilters, + ): Promise; getGateways(): Promise>; - getBalance({ address }: { address: WalletAddress }): Promise; - getBalances(): Promise>; - getRecord({ domain }: { domain: string }): Promise; + getBalance( + props: { address: WalletAddress } & ReadInteractionFilters, + ): Promise; + getBalances( + props: ReadInteractionFilters, + ): Promise>; + getRecord( + props: { domain: string } & ReadInteractionFilters, + ): Promise; getRecords(): Promise>; } @@ -45,11 +62,13 @@ export interface HTTPClient { signal, headers, allowedStatuses, + params, }: { endpoint: string; signal?: AbortSignal; headers?: Record; allowedStatuses?: number[]; + params?: Record; // eslint-disable-line @typescript-eslint/no-explicit-any }): Promise; // TODO: add post method // post({ diff --git a/src/utils/index.ts b/src/utils/index.ts index 4d2742ca..521e4f01 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -16,3 +16,4 @@ */ export * from './arweave.js'; export * from './http-client.js'; +export * from './smartweave/index.js'; diff --git a/src/utils/smartweave/evaluation.test.ts b/src/utils/smartweave/evaluation.test.ts new file mode 100644 index 00000000..dd8de782 --- /dev/null +++ b/src/utils/smartweave/evaluation.test.ts @@ -0,0 +1,13 @@ +import { SmartWeaveSortKey } from './evaluation.js'; + +describe(`Smartweave eval utils`, () => { + it(`should throw on a bad sort key`, async () => { + const sortKey = '123,456,abc'; + const error = await (async () => new SmartWeaveSortKey(sortKey))().catch( + (e) => e, + ); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toContain(sortKey); + }); +}); diff --git a/src/utils/smartweave/evaluation.ts b/src/utils/smartweave/evaluation.ts new file mode 100644 index 00000000..6cc902f6 --- /dev/null +++ b/src/utils/smartweave/evaluation.ts @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2022-2024 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 { SORT_KEY_REGEX } from '../../constants.js'; + +export class SmartWeaveSortKey { + private _sortKey: string; + constructor(sortKey: string) { + if (!SmartWeaveSortKey.validate(sortKey)) { + throw new Error(`Invalid sort key: ${sortKey}`); + } + + this._sortKey = sortKey; + } + + static validate(sortKey: string): boolean { + return SORT_KEY_REGEX.test(sortKey); + } + + toString(): string { + return this._sortKey; + } + + parts(): string[] { + return this._sortKey.split(','); + } + blockHeight(): number { + return parseInt(this.parts()[0]); + } + timestamp(): number { + return parseInt(this.parts()[1]); + } + hash(): string { + return this.parts()[2]; + } +} diff --git a/src/utils/smartweave/index.ts b/src/utils/smartweave/index.ts new file mode 100644 index 00000000..c0451be1 --- /dev/null +++ b/src/utils/smartweave/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright (C) 2022-2024 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 . + */ +export * from './evaluation.js'; diff --git a/tests/arns-remote-cache.test.ts b/tests/arns-remote-cache.test.ts deleted file mode 100644 index 610be2a8..00000000 --- a/tests/arns-remote-cache.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ArNSRemoteCache } from '../src/common/caches/arns-remote-cache.js'; -import { NotFound } from '../src/common/error.js'; - -describe('ArNSRemoteCache', () => { - const remoteCacheProvider = new ArNSRemoteCache({}); - - // gateway tests - it('should be able to fetch gateways', async () => { - const gateways = await remoteCacheProvider.getGateways(); - expect(gateways).toBeDefined(); - }); - - it('should should throw NotFound error on non existent gateway', async () => { - const error = await remoteCacheProvider - .getGateway({ - address: 'some-address', - }) - .catch((e) => e); - expect(error).toBeInstanceOf(NotFound); - }); - - // balance tests - it('should fetch a balance', async () => { - const balance = await remoteCacheProvider.getBalance({ - address: 'some-address', - }); - expect(balance).toEqual(0); - }); - - it('should fetch all balances', async () => { - const balances = await remoteCacheProvider.getBalances(); - expect(balances).toBeDefined(); - }); - - // records tests - it('should fetch a record', async () => { - const record = await remoteCacheProvider.getRecord({ - domain: 'ar-io', - }); - expect(record).toBeDefined(); - }); - - it('should throw NotFound error on non existent record', async () => { - const error = await remoteCacheProvider - .getRecord({ - domain: 'some-domain', - }) - .catch((e) => e); - expect(error).toBeInstanceOf(NotFound); - }); - - it('should fetch all records', async () => { - const records = await remoteCacheProvider.getRecords(); - - expect(records).toBeDefined(); - }); -}); diff --git a/tests/arns-remote-cache/balances.test.ts b/tests/arns-remote-cache/balances.test.ts new file mode 100644 index 00000000..cbcd3884 --- /dev/null +++ b/tests/arns-remote-cache/balances.test.ts @@ -0,0 +1,76 @@ +import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; +import { SmartWeaveSortKey } from '../../src/utils/index.js'; + +describe('ArNSRemoteCache ~ BALANCES', () => { + const remoteCacheProvider = new ArNSRemoteCache({}); + + // balance tests + it('should fetch a balance', async () => { + const balance = await remoteCacheProvider.getBalance({ + address: 'some-address', + }); + expect(balance).toEqual(0); + }); + + it('should fetch all balances', async () => { + const balances = await remoteCacheProvider.getBalances(); + expect(balances).toBeDefined(); + }); + + it('should return balance at a given block height', async () => { + const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + const currentBalance = 2_363_250; + const transferAmount = 1000; + const transferBlockHeight = 1305612; + const balance = await remoteCacheProvider.getBalance({ + address, + blockHeight: transferBlockHeight, + }); + expect(balance).toEqual(currentBalance); + + const previousBalance = await remoteCacheProvider.getBalance({ + address, + blockHeight: transferBlockHeight - 1, + }); + expect(previousBalance).toEqual(currentBalance + transferAmount); + }); + + it('should return balance at a given sort key', async () => { + const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + const balanceSortKey = new SmartWeaveSortKey( + '000001305612,0000000000000,6806919fa401ad27fd86db576ef578857bd22a11d6905324d643368069146d4e', + ); + const balance = await remoteCacheProvider.getBalance({ + address, + sortKey: balanceSortKey, + }); + expect(balance).toEqual(2363250); + }); + + it('should return balances at a given block height', async () => { + const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + const currentBalance = 2363250; + const transferAmount = 1000; + const transferBlockHeight = 1305612; + const balances = await remoteCacheProvider.getBalances({ + blockHeight: transferBlockHeight, + }); + expect(balances[address]).toEqual(currentBalance); + + const previousBalances = await remoteCacheProvider.getBalances({ + blockHeight: transferBlockHeight - 1, + }); + expect(previousBalances[address]).toEqual(currentBalance + transferAmount); + }); + + it('should return balances at a given sort key', async () => { + const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + const balanceSortKey = new SmartWeaveSortKey( + '000001305612,0000000000000,6806919fa401ad27fd86db576ef578857bd22a11d6905324d643368069146d4e', + ); + const balances = await remoteCacheProvider.getBalances({ + sortKey: balanceSortKey, + }); + expect(balances[address]).toEqual(2363250); + }); +}); diff --git a/tests/arns-remote-cache/gateways.test.ts b/tests/arns-remote-cache/gateways.test.ts new file mode 100644 index 00000000..04ba6979 --- /dev/null +++ b/tests/arns-remote-cache/gateways.test.ts @@ -0,0 +1,72 @@ +import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; +import { NotFound } from '../../src/common/error.js'; +import { SmartWeaveSortKey } from '../../src/utils/index.js'; + +describe('ArNSRemoteCache ~ GATEWAYS', () => { + const remoteCacheProvider = new ArNSRemoteCache({}); + + // gateway tests + it('should be able to fetch gateways', async () => { + const gateways = await remoteCacheProvider.getGateways(); + expect(gateways).toBeDefined(); + }); + + it('should should throw NotFound error on non existent gateway', async () => { + const error = await remoteCacheProvider + .getGateway({ + address: 'some-address', + }) + .catch((e) => e); + expect(error).toBeInstanceOf(NotFound); + }); + + it('should return gateway state at a given block height', async () => { + const blockHeight = 1372179; + const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + const gateway = await remoteCacheProvider.getGateway({ + address, + blockHeight, + }); + expect(gateway).toBeDefined(); + + const previousGatewayState = await remoteCacheProvider + .getGateway({ + address, + blockHeight: blockHeight - 1, + }) + .catch((e) => e); + expect(previousGatewayState).toBeInstanceOf(NotFound); + }); + + it('should return gateway state at a given sort key', async () => { + const sortKey = new SmartWeaveSortKey( + '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', + ); + const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + const gateway = await remoteCacheProvider.getGateway({ + address, + sortKey, + }); + expect(gateway).toBeDefined(); + }); + + it('should return gateways state at a given block height', async () => { + const blockHeight = 1372179; + const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + const gateways = await remoteCacheProvider.getGateways({ + blockHeight, + }); + expect(gateways[address]).toBeDefined(); + }); + + it('should return gateways state at a given sort key', async () => { + const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + const sortKey = new SmartWeaveSortKey( + '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', + ); + const gateways = await remoteCacheProvider.getGateways({ + sortKey, + }); + expect(gateways[address]).toBeDefined(); + }); +}); diff --git a/tests/arns-remote-cache/records.test.ts b/tests/arns-remote-cache/records.test.ts new file mode 100644 index 00000000..22955acb --- /dev/null +++ b/tests/arns-remote-cache/records.test.ts @@ -0,0 +1,81 @@ +import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; +import { NotFound } from '../../src/common/error.js'; +import { SmartWeaveSortKey } from '../../src/utils/index.js'; + +describe('ArNSRemoteCache ~ RECORDS', () => { + const remoteCacheProvider = new ArNSRemoteCache({}); + // records tests + it('should fetch a record', async () => { + const record = await remoteCacheProvider.getRecord({ + domain: 'ar-io', + }); + expect(record).toBeDefined(); + }); + + it('should throw NotFound error on non existent record', async () => { + const error = await remoteCacheProvider + .getRecord({ + domain: 'some-domain', + }) + .catch((e) => e); + expect(error).toBeInstanceOf(NotFound); + }); + + it('should fetch all records', async () => { + const records = await remoteCacheProvider.getRecords(); + + expect(records).toBeDefined(); + }); + + it('should return record at a given block height', async () => { + const domain = 'raiman'; + const registrationBlockHeight = 1372652; + const currentRecord = await remoteCacheProvider.getRecord({ + domain, + blockHeight: registrationBlockHeight, + }); + expect(currentRecord).toBeDefined(); + + const error = await remoteCacheProvider + .getRecord({ domain, blockHeight: registrationBlockHeight - 1 }) + .catch((e) => e); + expect(error).toBeInstanceOf(NotFound); + }); + + it('should return record at a given sort key', async () => { + const domain = 'raiman'; + const registrationSortKey = new SmartWeaveSortKey( + '000001372652,0000000000000,7c697ffe5ffdad0f554dbd4fe8aa4ac997ea58d34ff9bf54178ab894d47e41e8', + ); + const record = await remoteCacheProvider.getRecord({ + domain, + sortKey: registrationSortKey, + }); + expect(record).toBeDefined(); + }); + + it('should return records at a given block height', async () => { + const domain = 'raiman'; + const registrationBlockHeight = 1372652; + const currentRecords = await remoteCacheProvider.getRecords({ + blockHeight: registrationBlockHeight, + }); + expect(currentRecords[domain]).toBeDefined(); + + const previousRecords = await remoteCacheProvider.getRecords({ + blockHeight: registrationBlockHeight - 1, + }); + expect(previousRecords[domain]).not.toBeDefined(); + }); + + it('should return records at a given sort key', async () => { + const domain = 'raiman'; + const registrationSortKey = new SmartWeaveSortKey( + '000001372652,0000000000000,7c697ffe5ffdad0f554dbd4fe8aa4ac997ea58d34ff9bf54178ab894d47e41e8', + ); + const records = await remoteCacheProvider.getRecords({ + sortKey: registrationSortKey, + }); + expect(records[domain]).toBeDefined(); + }); +}); From 619c193d210536a2e4ee1308805ce348298b42c8 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 1 Mar 2024 12:26:36 -0600 Subject: [PATCH 02/18] fix(tests): update with new names on methods --- tests/ar-io.test.ts | 2 -- tests/arns-remote-cache/records.test.ts | 18 +++++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/ar-io.test.ts b/tests/ar-io.test.ts index cce59a37..b17a45c7 100644 --- a/tests/ar-io.test.ts +++ b/tests/ar-io.test.ts @@ -5,7 +5,5 @@ describe('ArIO Client', () => { const arioClient = new ArIO(); expect(arioClient).toBeInstanceOf(ArIO); - expect(arioClient.testnet).toBeDefined(); - expect(arioClient.devnet).toBeDefined(); }); }); diff --git a/tests/arns-remote-cache/records.test.ts b/tests/arns-remote-cache/records.test.ts index 22955acb..b1ac11f8 100644 --- a/tests/arns-remote-cache/records.test.ts +++ b/tests/arns-remote-cache/records.test.ts @@ -6,7 +6,7 @@ describe('ArNSRemoteCache ~ RECORDS', () => { const remoteCacheProvider = new ArNSRemoteCache({}); // records tests it('should fetch a record', async () => { - const record = await remoteCacheProvider.getRecord({ + const record = await remoteCacheProvider.getArNSRecord({ domain: 'ar-io', }); expect(record).toBeDefined(); @@ -14,7 +14,7 @@ describe('ArNSRemoteCache ~ RECORDS', () => { it('should throw NotFound error on non existent record', async () => { const error = await remoteCacheProvider - .getRecord({ + .getArNSRecord({ domain: 'some-domain', }) .catch((e) => e); @@ -22,7 +22,7 @@ describe('ArNSRemoteCache ~ RECORDS', () => { }); it('should fetch all records', async () => { - const records = await remoteCacheProvider.getRecords(); + const records = await remoteCacheProvider.getArNSRecords(); expect(records).toBeDefined(); }); @@ -30,14 +30,14 @@ describe('ArNSRemoteCache ~ RECORDS', () => { it('should return record at a given block height', async () => { const domain = 'raiman'; const registrationBlockHeight = 1372652; - const currentRecord = await remoteCacheProvider.getRecord({ + const currentRecord = await remoteCacheProvider.getArNSRecord({ domain, blockHeight: registrationBlockHeight, }); expect(currentRecord).toBeDefined(); const error = await remoteCacheProvider - .getRecord({ domain, blockHeight: registrationBlockHeight - 1 }) + .getArNSRecord({ domain, blockHeight: registrationBlockHeight - 1 }) .catch((e) => e); expect(error).toBeInstanceOf(NotFound); }); @@ -47,7 +47,7 @@ describe('ArNSRemoteCache ~ RECORDS', () => { const registrationSortKey = new SmartWeaveSortKey( '000001372652,0000000000000,7c697ffe5ffdad0f554dbd4fe8aa4ac997ea58d34ff9bf54178ab894d47e41e8', ); - const record = await remoteCacheProvider.getRecord({ + const record = await remoteCacheProvider.getArNSRecord({ domain, sortKey: registrationSortKey, }); @@ -57,12 +57,12 @@ describe('ArNSRemoteCache ~ RECORDS', () => { it('should return records at a given block height', async () => { const domain = 'raiman'; const registrationBlockHeight = 1372652; - const currentRecords = await remoteCacheProvider.getRecords({ + const currentRecords = await remoteCacheProvider.getArNSRecords({ blockHeight: registrationBlockHeight, }); expect(currentRecords[domain]).toBeDefined(); - const previousRecords = await remoteCacheProvider.getRecords({ + const previousRecords = await remoteCacheProvider.getArNSRecords({ blockHeight: registrationBlockHeight - 1, }); expect(previousRecords[domain]).not.toBeDefined(); @@ -73,7 +73,7 @@ describe('ArNSRemoteCache ~ RECORDS', () => { const registrationSortKey = new SmartWeaveSortKey( '000001372652,0000000000000,7c697ffe5ffdad0f554dbd4fe8aa4ac997ea58d34ff9bf54178ab894d47e41e8', ); - const records = await remoteCacheProvider.getRecords({ + const records = await remoteCacheProvider.getArNSRecords({ sortKey: registrationSortKey, }); expect(records[domain]).toBeDefined(); From 7d14edb55c7db6a949d717fe19ffe1ae87916a9d Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 1 Mar 2024 13:24:50 -0600 Subject: [PATCH 03/18] fix(types): remove unnecesssary empty defaults --- src/common/caches/arns-remote-cache.ts | 6 +++--- src/types/common.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/common/caches/arns-remote-cache.ts b/src/common/caches/arns-remote-cache.ts index 70e8355e..7b5f316e 100644 --- a/src/common/caches/arns-remote-cache.ts +++ b/src/common/caches/arns-remote-cache.ts @@ -76,7 +76,7 @@ export class ArNSRemoteCache implements ArIOContract { return gateway; } - async getGateways({ blockHeight, sortKey }: ReadInteractionFilters = {}) { + async getGateways({ blockHeight, sortKey }: ReadInteractionFilters) { this.logger.debug(`Fetching gateways`); const { result } = await this.http.get< ArNSStateResponse<'result', Record> @@ -107,7 +107,7 @@ export class ArNSRemoteCache implements ArIOContract { return result; } - async getBalances({ blockHeight, sortKey }: ReadInteractionFilters = {}) { + async getBalances({ blockHeight, sortKey }: ReadInteractionFilters) { this.logger.debug(`Fetching balances`); const { result } = await this.http.get< ArNSStateResponse<'result', Record> @@ -136,7 +136,7 @@ export class ArNSRemoteCache implements ArIOContract { async getArNSRecords({ blockHeight, sortKey, - }: ReadInteractionFilters = {}): Promise> { + }: ReadInteractionFilters): Promise> { this.logger.debug(`Fetching all records`); const { result } = await this.http.get< ArNSStateResponse<'result', Record> diff --git a/src/types/common.ts b/src/types/common.ts index d612038f..45df227a 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -30,7 +30,9 @@ export interface ArIOContract { getGateway( props: { address: WalletAddress } & ReadInteractionFilters, ): Promise; - getGateways(): Promise>; + getGateways( + props: ReadInteractionFilters, + ): Promise>; getBalance( props: { address: WalletAddress } & ReadInteractionFilters, ): Promise; @@ -40,7 +42,9 @@ export interface ArIOContract { getArNSRecord( props: { domain: string } & ReadInteractionFilters, ): Promise; - getArNSRecords(): Promise>; + getArNSRecords( + prop: ReadInteractionFilters, + ): Promise>; } /* eslint-disable @typescript-eslint/no-explicit-any */ From 5c8024261b5e13ee4f4d697b545a710b7cf136d1 Mon Sep 17 00:00:00 2001 From: atticusofsparta Date: Fri, 1 Mar 2024 13:25:43 -0600 Subject: [PATCH 04/18] fix(types): remove any type --- src/common/http.ts | 2 +- src/types/common.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/http.ts b/src/common/http.ts index ebbf4b6c..f6aa7a99 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -45,7 +45,7 @@ export class AxiosHTTPService implements HTTPClient { signal?: AbortSignal; allowedStatuses?: number[]; headers?: Record; - params?: Record; // eslint-disable-line @typescript-eslint/no-explicit-any + params?: Record; }): Promise { this.logger.debug(`Get request to endpoint: ${endpoint}`); const { status, statusText, data } = await this.axios.get(endpoint, { diff --git a/src/types/common.ts b/src/types/common.ts index 45df227a..5c860f79 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -72,7 +72,7 @@ export interface HTTPClient { signal?: AbortSignal; headers?: Record; allowedStatuses?: number[]; - params?: Record; // eslint-disable-line @typescript-eslint/no-explicit-any + params?: Record; }): Promise; // TODO: add post method // post({ From f8ff552ed6de9516476e74e6aad92e72a8d3adb3 Mon Sep 17 00:00:00 2001 From: Atticus Date: Mon, 4 Mar 2024 11:33:48 -0500 Subject: [PATCH 05/18] fix(types): make props nullable on certain read apis --- src/types/common.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/common.ts b/src/types/common.ts index 5c860f79..cfc86ebf 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -31,19 +31,19 @@ export interface ArIOContract { props: { address: WalletAddress } & ReadInteractionFilters, ): Promise; getGateways( - props: ReadInteractionFilters, + props?: ReadInteractionFilters, ): Promise>; getBalance( props: { address: WalletAddress } & ReadInteractionFilters, ): Promise; getBalances( - props: ReadInteractionFilters, + props?: ReadInteractionFilters, ): Promise>; getArNSRecord( props: { domain: string } & ReadInteractionFilters, ): Promise; getArNSRecords( - prop: ReadInteractionFilters, + props?: ReadInteractionFilters, ): Promise>; } From ae890c85f5fca785f88c60a751fb617b2389de61 Mon Sep 17 00:00:00 2001 From: Atticus Date: Tue, 5 Mar 2024 10:34:11 -0500 Subject: [PATCH 06/18] fix(tests): update tests to use younger contract, add evalParams config --- src/common/ar-io.ts | 35 +++++--- src/common/caches/arns-remote-cache.ts | 68 +++++++++++---- src/common/http.ts | 4 +- src/types/common.ts | 29 ++++--- src/utils/arweave.ts | 5 ++ src/utils/smartweave/evaluation.ts | 5 ++ tests/arns-remote-cache/balances.test.ts | 68 ++++++++------- tests/arns-remote-cache/gateways.test.ts | 100 ++++++++++++----------- tests/arns-remote-cache/records.test.ts | 48 +++++++---- 9 files changed, 227 insertions(+), 135 deletions(-) diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index c0d3b7ea..afcfc930 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -14,7 +14,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { ArIOContract, ArNSNameData, Gateway } from '../types/index.js'; +import { + ArIOContract, + ArNSNameData, + Gateway, + ReadInteractionFilters, +} from '../types/index.js'; import { ArNSRemoteCache } from './index.js'; export type CacheConfiguration = { @@ -36,22 +41,30 @@ export class ArIO implements ArIOContract { } // implement ArIOContract interface - async getArNSRecord({ domain }: { domain: string }): Promise { - return this.cache.getArNSRecord({ domain }); + async getArNSRecord( + params: { domain: string } & ReadInteractionFilters, + ): Promise { + return this.cache.getArNSRecord(params); } - async getArNSRecords(): Promise> { - return this.cache.getArNSRecords(); + async getArNSRecords( + params: ReadInteractionFilters, + ): Promise> { + return this.cache.getArNSRecords(params); } - async getBalance({ address }: { address: string }): Promise { - return this.cache.getBalance({ address }); + async getBalance( + params: { address: string } & ReadInteractionFilters, + ): Promise { + return this.cache.getBalance(params); } async getBalances(): Promise> { return this.cache.getBalances(); } - async getGateway({ address }: { address: string }): Promise { - return this.cache.getGateway({ address }); + async getGateway(params: { address: string }): Promise { + return this.cache.getGateway(params); } - async getGateways(): Promise> { - return this.cache.getGateways(); + async getGateways( + params: ReadInteractionFilters, + ): Promise> { + return this.cache.getGateways(params); } } diff --git a/src/common/caches/arns-remote-cache.ts b/src/common/caches/arns-remote-cache.ts index 7b5f316e..8589aaa6 100644 --- a/src/common/caches/arns-remote-cache.ts +++ b/src/common/caches/arns-remote-cache.ts @@ -19,10 +19,12 @@ import { ArIOContract, ArNSNameData, ArNSStateResponse, + EvalToParams, Gateway, HTTPClient, ReadInteractionFilters, } from '../../types/index.js'; +import { isBlockHeight, isSortKey } from '../../utils/index.js'; import { NotFound } from '../error.js'; import { AxiosHTTPService } from '../http.js'; import { DefaultLogger } from '../logger.js'; @@ -52,7 +54,15 @@ export class ArNSRemoteCache implements ArIOContract { logger, }); } - + sortKeyOrBlockHeightParams(historicalIndex: any): EvalToParams { + if (isSortKey(historicalIndex?.sortKey)) { + return { sortKey: historicalIndex.sortKey }; + } + if (isBlockHeight(historicalIndex?.blockHeight)) { + return { blockHeight: historicalIndex.blockHeight }; + } + return {}; + } private validateContractTxId(id: string) { if (!ARWEAVE_TX_REGEX.test(id)) { throw new Error(`Invalid contract tx id: ${id}`); @@ -61,11 +71,11 @@ export class ArNSRemoteCache implements ArIOContract { async getGateway({ address, - blockHeight, - sortKey, + evaluationParameters, }: { address: string } & ReadInteractionFilters) { this.logger.debug(`Fetching gateway ${address}`); - const gateway = await this.getGateways({ blockHeight, sortKey }).then( + + const gateway = await this.getGateways({ evaluationParameters }).then( (gateways) => { if (gateways[address] === undefined) { throw new NotFound(`Gateway not found: ${address}`); @@ -76,27 +86,36 @@ export class ArNSRemoteCache implements ArIOContract { return gateway; } - async getGateways({ blockHeight, sortKey }: ReadInteractionFilters) { + async getGateways({ evaluationParameters }: ReadInteractionFilters = {}) { this.logger.debug(`Fetching gateways`); + + const params = this.sortKeyOrBlockHeightParams( + evaluationParameters?.evalTo, + ); + const { result } = await this.http.get< ArNSStateResponse<'result', Record> >({ endpoint: `/contract/${this.contractTxId.toString()}/read/gateways`, - params: { blockHeight, sortKey: sortKey?.toString() }, + params, }); return result; } async getBalance({ address, - blockHeight, - sortKey, + evaluationParameters, }: { address: string } & ReadInteractionFilters) { this.logger.debug(`Fetching balance for ${address}`); + + const params = this.sortKeyOrBlockHeightParams( + evaluationParameters?.evalTo, + ); + const { result } = await this.http .get>({ endpoint: `/contract/${this.contractTxId.toString()}/state/balances/${address}`, - params: { blockHeight, sortKey: sortKey?.toString() }, + params, }) .catch((e) => { if (e instanceof NotFound) { @@ -107,42 +126,55 @@ export class ArNSRemoteCache implements ArIOContract { return result; } - async getBalances({ blockHeight, sortKey }: ReadInteractionFilters) { + async getBalances({ evaluationParameters }: ReadInteractionFilters = {}) { this.logger.debug(`Fetching balances`); + + const params = this.sortKeyOrBlockHeightParams( + evaluationParameters?.evalTo, + ); + const { result } = await this.http.get< ArNSStateResponse<'result', Record> >({ endpoint: `/contract/${this.contractTxId.toString()}/state/balances`, - params: { blockHeight, sortKey: sortKey?.toString() }, + params, }); return result; } async getArNSRecord({ domain, - blockHeight, - sortKey, + evaluationParameters, }: { domain: string } & ReadInteractionFilters): Promise { this.logger.debug(`Fetching record for ${domain}`); + + const params = this.sortKeyOrBlockHeightParams( + evaluationParameters?.evalTo, + ); + const { result } = await this.http.get< ArNSStateResponse<'result', ArNSNameData> >({ endpoint: `/contract/${this.contractTxId.toString()}/state/records/${domain}`, - params: { blockHeight, sortKey: sortKey?.toString() }, + params, }); return result; } async getArNSRecords({ - blockHeight, - sortKey, - }: ReadInteractionFilters): Promise> { + evaluationParameters, + }: ReadInteractionFilters = {}): Promise> { this.logger.debug(`Fetching all records`); + + const params = this.sortKeyOrBlockHeightParams( + evaluationParameters?.evalTo, + ); + const { result } = await this.http.get< ArNSStateResponse<'result', Record> >({ endpoint: `/contract/${this.contractTxId.toString()}/state/records`, - params: { blockHeight, sortKey: sortKey?.toString() }, + params, }); return result; } diff --git a/src/common/http.ts b/src/common/http.ts index f6aa7a99..30ee90c0 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -47,7 +47,9 @@ export class AxiosHTTPService implements HTTPClient { headers?: Record; params?: Record; }): Promise { - this.logger.debug(`Get request to endpoint: ${endpoint}`); + this.logger.debug( + `Get request to endpoint: ${endpoint} with params ${JSON.stringify(params, undefined, 2)}`, + ); const { status, statusText, data } = await this.axios.get(endpoint, { headers, signal, diff --git a/src/types/common.ts b/src/types/common.ts index cfc86ebf..7529e45e 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -14,36 +14,43 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { SmartWeaveSortKey } from '../utils/index.js'; import { ArNSNameData, Gateway } from './contract-state.js'; -export type EvaluationFilters = { - blockHeight?: number; - sortKey?: SmartWeaveSortKey; // should be tested against regex for validity +export type BlockHeight = number; +export type SortKey = string; + +export type EvalToParams = + | { sortKey?: SortKey } + | { blockHeight?: BlockHeight }; + +export type EvaluationParameters = { + evalTo?: EvalToParams; }; // TODO: extend type with other read filters (e.g max eval time) -export type ReadInteractionFilters = EvaluationFilters; +export type ReadInteractionFilters = { + evaluationParameters?: EvaluationParameters; +}; // TODO: extend with additional methods export interface ArIOContract { getGateway( - props: { address: WalletAddress } & ReadInteractionFilters, + params: { address: WalletAddress } & ReadInteractionFilters, ): Promise; getGateways( - props?: ReadInteractionFilters, + params?: ReadInteractionFilters, ): Promise>; getBalance( - props: { address: WalletAddress } & ReadInteractionFilters, + params: { address: WalletAddress } & ReadInteractionFilters, ): Promise; getBalances( - props?: ReadInteractionFilters, + params?: ReadInteractionFilters, ): Promise>; getArNSRecord( - props: { domain: string } & ReadInteractionFilters, + params: { domain: string } & ReadInteractionFilters, ): Promise; getArNSRecords( - props?: ReadInteractionFilters, + params?: ReadInteractionFilters, ): Promise>; } diff --git a/src/utils/arweave.ts b/src/utils/arweave.ts index 1e25fb4a..ae1dae83 100644 --- a/src/utils/arweave.ts +++ b/src/utils/arweave.ts @@ -15,7 +15,12 @@ * along with this program. If not, see . */ import { ARWEAVE_TX_REGEX } from '../constants.js'; +import { BlockHeight } from '../types/common.js'; export const validateArweaveId = (id: string): boolean => { return ARWEAVE_TX_REGEX.test(id); }; + +export function isBlockHeight(height: string | number): height is BlockHeight { + return height !== undefined; +} diff --git a/src/utils/smartweave/evaluation.ts b/src/utils/smartweave/evaluation.ts index 6cc902f6..4d1deddc 100644 --- a/src/utils/smartweave/evaluation.ts +++ b/src/utils/smartweave/evaluation.ts @@ -15,6 +15,11 @@ * along with this program. If not, see . */ import { SORT_KEY_REGEX } from '../../constants.js'; +import { SortKey } from '../../types/common.js'; + +export function isSortKey(sortKey: string): sortKey is SortKey { + return SmartWeaveSortKey.validate(sortKey); +} export class SmartWeaveSortKey { private _sortKey: string; diff --git a/tests/arns-remote-cache/balances.test.ts b/tests/arns-remote-cache/balances.test.ts index cbcd3884..253fafff 100644 --- a/tests/arns-remote-cache/balances.test.ts +++ b/tests/arns-remote-cache/balances.test.ts @@ -1,9 +1,11 @@ import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; +import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; import { SmartWeaveSortKey } from '../../src/utils/index.js'; describe('ArNSRemoteCache ~ BALANCES', () => { - const remoteCacheProvider = new ArNSRemoteCache({}); - + const remoteCacheProvider = new ArNSRemoteCache({ + contractTxId: ARNS_DEVNET_REGISTRY_TX, + }); // balance tests it('should fetch a balance', async () => { const balance = await remoteCacheProvider.getBalance({ @@ -18,59 +20,63 @@ describe('ArNSRemoteCache ~ BALANCES', () => { }); it('should return balance at a given block height', async () => { - const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; - const currentBalance = 2_363_250; - const transferAmount = 1000; - const transferBlockHeight = 1305612; + const address = 'ySqMsg7O0R-BcUw35R3nxJJKJyIdauLCQ4DUZqPCiYo'; + const transferBlockHeight = 1364752; + const currentBalance = await remoteCacheProvider.getBalance({ + address, + evaluationParameters: { + evalTo: { blockHeight: transferBlockHeight }, + }, + }); + const transferAmount = 20000; + const balance = await remoteCacheProvider.getBalance({ address, - blockHeight: transferBlockHeight, + evaluationParameters: { evalTo: { blockHeight: transferBlockHeight } }, }); expect(balance).toEqual(currentBalance); const previousBalance = await remoteCacheProvider.getBalance({ address, - blockHeight: transferBlockHeight - 1, + evaluationParameters: { + evalTo: { blockHeight: transferBlockHeight - 1 }, + }, }); - expect(previousBalance).toEqual(currentBalance + transferAmount); + expect(previousBalance).toEqual(currentBalance - transferAmount); }); it('should return balance at a given sort key', async () => { - const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; + const address = 'ySqMsg7O0R-BcUw35R3nxJJKJyIdauLCQ4DUZqPCiYo'; const balanceSortKey = new SmartWeaveSortKey( - '000001305612,0000000000000,6806919fa401ad27fd86db576ef578857bd22a11d6905324d643368069146d4e', + '000001364752,0000000000000,7fee05ef004191b252b073628013f987033513c51116d283dc24c866b5c32d0a', ); const balance = await remoteCacheProvider.getBalance({ address, - sortKey: balanceSortKey, + evaluationParameters: { evalTo: { sortKey: balanceSortKey.toString() } }, }); - expect(balance).toEqual(2363250); + expect(balance).toEqual(20000); }); it('should return balances at a given block height', async () => { - const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; - const currentBalance = 2363250; - const transferAmount = 1000; - const transferBlockHeight = 1305612; + const address = 'ySqMsg7O0R-BcUw35R3nxJJKJyIdauLCQ4DUZqPCiYo'; + const transferBlockHeight = 1364752; + const currentBalance = await remoteCacheProvider.getBalance({ + address, + evaluationParameters: { + evalTo: { blockHeight: transferBlockHeight }, + }, + }); const balances = await remoteCacheProvider.getBalances({ - blockHeight: transferBlockHeight, + evaluationParameters: { evalTo: { blockHeight: transferBlockHeight } }, }); + expect(balances[address]).toEqual(currentBalance); const previousBalances = await remoteCacheProvider.getBalances({ - blockHeight: transferBlockHeight - 1, - }); - expect(previousBalances[address]).toEqual(currentBalance + transferAmount); - }); - - it('should return balances at a given sort key', async () => { - const address = '7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5-5dV7nk'; - const balanceSortKey = new SmartWeaveSortKey( - '000001305612,0000000000000,6806919fa401ad27fd86db576ef578857bd22a11d6905324d643368069146d4e', - ); - const balances = await remoteCacheProvider.getBalances({ - sortKey: balanceSortKey, + evaluationParameters: { + evalTo: { blockHeight: transferBlockHeight - 1 }, + }, }); - expect(balances[address]).toEqual(2363250); + expect(previousBalances[address]).toEqual(undefined); }); }); diff --git a/tests/arns-remote-cache/gateways.test.ts b/tests/arns-remote-cache/gateways.test.ts index 04ba6979..01f4e5fa 100644 --- a/tests/arns-remote-cache/gateways.test.ts +++ b/tests/arns-remote-cache/gateways.test.ts @@ -1,10 +1,11 @@ import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; import { NotFound } from '../../src/common/error.js'; -import { SmartWeaveSortKey } from '../../src/utils/index.js'; +import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; describe('ArNSRemoteCache ~ GATEWAYS', () => { - const remoteCacheProvider = new ArNSRemoteCache({}); - + const remoteCacheProvider = new ArNSRemoteCache({ + contractTxId: ARNS_DEVNET_REGISTRY_TX, + }); // gateway tests it('should be able to fetch gateways', async () => { const gateways = await remoteCacheProvider.getGateways(); @@ -20,53 +21,56 @@ describe('ArNSRemoteCache ~ GATEWAYS', () => { expect(error).toBeInstanceOf(NotFound); }); - it('should return gateway state at a given block height', async () => { - const blockHeight = 1372179; - const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - const gateway = await remoteCacheProvider.getGateway({ - address, - blockHeight, - }); - expect(gateway).toBeDefined(); + // TODO: add blockheight and sortkey tests when simulation with docker is added. current devnet contract doesnt have read api available at last interaction points to test. + // it('should return gateway state at a given block height', async () => { + // const blockHeight = 1348901; + // const address = '1H7WZIWhzwTH9FIcnuMqYkTsoyv1OTfGa_amvuYwrgo'; + // const currentStake = 95048; + // const stakeIncrease = 51250; + // const gateway = await remoteCacheProvider.getGateway({ + // address, + // evaluationParameters: { evalTo: { blockHeight } }, + // }); + // expect(gateway.operatorStake).toEqual(currentStake); - const previousGatewayState = await remoteCacheProvider - .getGateway({ - address, - blockHeight: blockHeight - 1, - }) - .catch((e) => e); - expect(previousGatewayState).toBeInstanceOf(NotFound); - }); + // const previousGatewayState = await remoteCacheProvider + // .getGateway({ + // address, + // evaluationParameters: { evalTo: { blockHeight: blockHeight - 1 } }, + // }) + // .catch((e) => e); + // expect(previousGatewayState.operatorStake).toEqual(currentStake - stakeIncrease); + // }); - it('should return gateway state at a given sort key', async () => { - const sortKey = new SmartWeaveSortKey( - '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', - ); - const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - const gateway = await remoteCacheProvider.getGateway({ - address, - sortKey, - }); - expect(gateway).toBeDefined(); - }); + // it('should return gateway state at a given sort key', async () => { + // const sortKey = new SmartWeaveSortKey( + // '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', + // ); + // const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + // const gateway = await remoteCacheProvider.getGateway({ + // address, + // evaluationParameters: { evalTo: { sortKey: sortKey.toString() } }, + // }); + // expect(gateway).toBeDefined(); + // }); - it('should return gateways state at a given block height', async () => { - const blockHeight = 1372179; - const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - const gateways = await remoteCacheProvider.getGateways({ - blockHeight, - }); - expect(gateways[address]).toBeDefined(); - }); + // it('should return gateways state at a given block height', async () => { + // const blockHeight = 1372179; + // const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + // const gateways = await remoteCacheProvider.getGateways({ + // evaluationParameters: { evalTo: { blockHeight } }, + // }); + // expect(gateways[address]).toBeDefined(); + // }); - it('should return gateways state at a given sort key', async () => { - const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - const sortKey = new SmartWeaveSortKey( - '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', - ); - const gateways = await remoteCacheProvider.getGateways({ - sortKey, - }); - expect(gateways[address]).toBeDefined(); - }); + // it('should return gateways state at a given sort key', async () => { + // const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; + // const sortKey = new SmartWeaveSortKey( + // '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', + // ); + // const gateways = await remoteCacheProvider.getGateways({ + // evaluationParameters: { evalTo: { sortKey: sortKey.toString() } }, + // }); + // expect(gateways[address]).toBeDefined(); + // }); }); diff --git a/tests/arns-remote-cache/records.test.ts b/tests/arns-remote-cache/records.test.ts index b1ac11f8..d093e7d5 100644 --- a/tests/arns-remote-cache/records.test.ts +++ b/tests/arns-remote-cache/records.test.ts @@ -1,9 +1,12 @@ import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; import { NotFound } from '../../src/common/error.js'; +import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; import { SmartWeaveSortKey } from '../../src/utils/index.js'; describe('ArNSRemoteCache ~ RECORDS', () => { - const remoteCacheProvider = new ArNSRemoteCache({}); + const remoteCacheProvider = new ArNSRemoteCache({ + contractTxId: ARNS_DEVNET_REGISTRY_TX, + }); // records tests it('should fetch a record', async () => { const record = await remoteCacheProvider.getArNSRecord({ @@ -28,53 +31,68 @@ describe('ArNSRemoteCache ~ RECORDS', () => { }); it('should return record at a given block height', async () => { - const domain = 'raiman'; - const registrationBlockHeight = 1372652; + const domain = 'testing5'; + const registrationBlockHeight = 1363242; const currentRecord = await remoteCacheProvider.getArNSRecord({ domain, - blockHeight: registrationBlockHeight, + evaluationParameters: { + evalTo: { blockHeight: registrationBlockHeight + 1 }, + }, }); expect(currentRecord).toBeDefined(); const error = await remoteCacheProvider - .getArNSRecord({ domain, blockHeight: registrationBlockHeight - 1 }) + .getArNSRecord({ + domain, + evaluationParameters: { + evalTo: { blockHeight: registrationBlockHeight - 1 }, + }, + }) .catch((e) => e); expect(error).toBeInstanceOf(NotFound); }); it('should return record at a given sort key', async () => { - const domain = 'raiman'; + const domain = 'testing5'; const registrationSortKey = new SmartWeaveSortKey( - '000001372652,0000000000000,7c697ffe5ffdad0f554dbd4fe8aa4ac997ea58d34ff9bf54178ab894d47e41e8', + '000001363242,0000000000000,e7ac482567afa26cf205b158af46bf99f12b1dea0c1dd00caf9a573c8e648430', ); const record = await remoteCacheProvider.getArNSRecord({ domain, - sortKey: registrationSortKey, + evaluationParameters: { + evalTo: { sortKey: registrationSortKey.toString() }, + }, }); expect(record).toBeDefined(); }); it('should return records at a given block height', async () => { - const domain = 'raiman'; - const registrationBlockHeight = 1372652; + const domain = 'testing5'; + const registrationBlockHeight = 1363242; const currentRecords = await remoteCacheProvider.getArNSRecords({ - blockHeight: registrationBlockHeight, + evaluationParameters: { + evalTo: { blockHeight: registrationBlockHeight }, + }, }); expect(currentRecords[domain]).toBeDefined(); const previousRecords = await remoteCacheProvider.getArNSRecords({ - blockHeight: registrationBlockHeight - 1, + evaluationParameters: { + evalTo: { blockHeight: registrationBlockHeight - 1 }, + }, }); expect(previousRecords[domain]).not.toBeDefined(); }); it('should return records at a given sort key', async () => { - const domain = 'raiman'; + const domain = 'testing5'; const registrationSortKey = new SmartWeaveSortKey( - '000001372652,0000000000000,7c697ffe5ffdad0f554dbd4fe8aa4ac997ea58d34ff9bf54178ab894d47e41e8', + '000001363242,0000000000000,e7ac482567afa26cf205b158af46bf99f12b1dea0c1dd00caf9a573c8e648430', ); const records = await remoteCacheProvider.getArNSRecords({ - sortKey: registrationSortKey, + evaluationParameters: { + evalTo: { sortKey: registrationSortKey.toString() }, + }, }); expect(records[domain]).toBeDefined(); }); From 1fcb3e6c52c47d5c2e9b7df32eff944ec511fec6 Mon Sep 17 00:00:00 2001 From: Atticus Date: Tue, 5 Mar 2024 12:50:11 -0500 Subject: [PATCH 07/18] fix(tests): update gateways test --- tests/arns-remote-cache/gateways.test.ts | 89 +++++++++++------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/tests/arns-remote-cache/gateways.test.ts b/tests/arns-remote-cache/gateways.test.ts index 01f4e5fa..9a0fb742 100644 --- a/tests/arns-remote-cache/gateways.test.ts +++ b/tests/arns-remote-cache/gateways.test.ts @@ -1,11 +1,19 @@ import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; import { NotFound } from '../../src/common/error.js'; import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; +import { SmartWeaveSortKey } from '../../src/utils/index.js'; describe('ArNSRemoteCache ~ GATEWAYS', () => { const remoteCacheProvider = new ArNSRemoteCache({ contractTxId: ARNS_DEVNET_REGISTRY_TX, }); + const address = '1H7WZIWhzwTH9FIcnuMqYkTsoyv1OTfGa_amvuYwrgo'; + const currentBlockHeight = 1377100; + const previousBlockHeight = currentBlockHeight + 100; + const currentSortKey = new SmartWeaveSortKey( + '000001376946,0000000000000,18d52956c8e13ae1f557b4e67f6f298b8ffd2a5cd96e42ec24ca649b7401510f', + ); + // gateway tests it('should be able to fetch gateways', async () => { const gateways = await remoteCacheProvider.getGateways(); @@ -21,56 +29,41 @@ describe('ArNSRemoteCache ~ GATEWAYS', () => { expect(error).toBeInstanceOf(NotFound); }); - // TODO: add blockheight and sortkey tests when simulation with docker is added. current devnet contract doesnt have read api available at last interaction points to test. - // it('should return gateway state at a given block height', async () => { - // const blockHeight = 1348901; - // const address = '1H7WZIWhzwTH9FIcnuMqYkTsoyv1OTfGa_amvuYwrgo'; - // const currentStake = 95048; - // const stakeIncrease = 51250; - // const gateway = await remoteCacheProvider.getGateway({ - // address, - // evaluationParameters: { evalTo: { blockHeight } }, - // }); - // expect(gateway.operatorStake).toEqual(currentStake); + it('should return gateway state at a given block height', async () => { + const gateway = await remoteCacheProvider.getGateway({ + address, + evaluationParameters: { evalTo: { blockHeight: currentBlockHeight } }, + }); - // const previousGatewayState = await remoteCacheProvider - // .getGateway({ - // address, - // evaluationParameters: { evalTo: { blockHeight: blockHeight - 1 } }, - // }) - // .catch((e) => e); - // expect(previousGatewayState.operatorStake).toEqual(currentStake - stakeIncrease); - // }); + const previousGatewayState = await remoteCacheProvider.getGateway({ + address, + evaluationParameters: { evalTo: { blockHeight: previousBlockHeight } }, + }); + expect( + previousGatewayState.weights.tenureWeight === + gateway.weights.tenureWeight, + ).toBe(false); + }); - // it('should return gateway state at a given sort key', async () => { - // const sortKey = new SmartWeaveSortKey( - // '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', - // ); - // const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - // const gateway = await remoteCacheProvider.getGateway({ - // address, - // evaluationParameters: { evalTo: { sortKey: sortKey.toString() } }, - // }); - // expect(gateway).toBeDefined(); - // }); + it('should return gateway state at a given sort key', async () => { + const gateway = await remoteCacheProvider.getGateway({ + address, + evaluationParameters: { evalTo: { sortKey: currentSortKey.toString() } }, + }); + expect(gateway).toBeDefined(); + }); - // it('should return gateways state at a given block height', async () => { - // const blockHeight = 1372179; - // const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - // const gateways = await remoteCacheProvider.getGateways({ - // evaluationParameters: { evalTo: { blockHeight } }, - // }); - // expect(gateways[address]).toBeDefined(); - // }); + it('should return gateways state at a given block height', async () => { + const gateways = await remoteCacheProvider.getGateways({ + evaluationParameters: { evalTo: { blockHeight: currentBlockHeight } }, + }); + expect(gateways[address]).toBeDefined(); + }); - // it('should return gateways state at a given sort key', async () => { - // const address = 'usOg4jFzqinXK_ExoU5NijjEyggNA255998LNiM8Vtc'; - // const sortKey = new SmartWeaveSortKey( - // '000001372179,0000000000000,1babf113056ce4d158c06f17ac8a1d0bff603dd6218dad98381d8e6d295f50a5', - // ); - // const gateways = await remoteCacheProvider.getGateways({ - // evaluationParameters: { evalTo: { sortKey: sortKey.toString() } }, - // }); - // expect(gateways[address]).toBeDefined(); - // }); + it('should return gateways state at a given sort key', async () => { + const gateways = await remoteCacheProvider.getGateways({ + evaluationParameters: { evalTo: { sortKey: currentSortKey.toString() } }, + }); + expect(gateways[address]).toBeDefined(); + }); }); From 2b28675fabb97be0cadfbc8a47241b0bcc5bd9fd Mon Sep 17 00:00:00 2001 From: Atticus Date: Tue, 5 Mar 2024 13:56:07 -0500 Subject: [PATCH 08/18] fix(validity util): isBlockheight check more strict --- src/utils/arweave.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/arweave.ts b/src/utils/arweave.ts index ae1dae83..f5df514f 100644 --- a/src/utils/arweave.ts +++ b/src/utils/arweave.ts @@ -22,5 +22,5 @@ export const validateArweaveId = (id: string): boolean => { }; export function isBlockHeight(height: string | number): height is BlockHeight { - return height !== undefined; + return height !== undefined && !isNaN(parseInt(height.toString())); } From 855da2d1ce53ade61025e9a2513ac706e362e0b1 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 5 Mar 2024 18:01:28 -0700 Subject: [PATCH 09/18] feat(contract): create new contract classes that impelement both warp and remote cache for ant contract and ar-io contracts --- src/common/ar-io.ts | 113 +++++++++++---- src/common/caches/arns-remote-cache.ts | 181 ------------------------- src/common/caches/remote-contract.ts | 88 ++++++++++++ src/common/caches/warp-contract.ts | 86 ++++++++++++ src/common/http.ts | 35 ----- src/types/common.ts | 63 +++++---- src/types/contract-state.ts | 16 ++- 7 files changed, 312 insertions(+), 270 deletions(-) delete mode 100644 src/common/caches/arns-remote-cache.ts create mode 100644 src/common/caches/remote-contract.ts create mode 100644 src/common/caches/warp-contract.ts diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index afcfc930..455bbfa8 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -14,57 +14,116 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { ARNS_TESTNET_REGISTRY_TX } from '../constants.js'; import { + AntState, ArIOContract, + ArIOState, ArNSNameData, + EvaluationOptions, Gateway, - ReadInteractionFilters, + AntContract as IAntContract, + SmartWeaveContract, } from '../types/index.js'; -import { ArNSRemoteCache } from './index.js'; +import { ArIOServiceContract } from './index.js'; -export type CacheConfiguration = { - remoteCacheUrl?: string; - contractTxId?: string; -}; -export type ArIOConfiguration = { - cacheConfig?: CacheConfiguration; +export type ContractConfiguration = { + contract?: SmartWeaveContract; }; export class ArIO implements ArIOContract { - protected cache: ArIOContract; + private contract: SmartWeaveContract; - constructor({ cacheConfig }: ArIOConfiguration = {}) { - this.cache = new ArNSRemoteCache({ - contractTxId: cacheConfig?.contractTxId, - url: cacheConfig?.remoteCacheUrl, - }); + constructor({ + contract = new ArIOServiceContract({ + contractTxId: ARNS_TESTNET_REGISTRY_TX, + }), + }: ContractConfiguration) { + this.contract = contract; } - // implement ArIOContract interface + async getState(params: EvaluationOptions): Promise { + return this.contract.getContractState(params); + } async getArNSRecord( - params: { domain: string } & ReadInteractionFilters, + params: { domain: string } & EvaluationOptions, ): Promise { - return this.cache.getArNSRecord(params); + const records = await this.getArNSRecords(params); + return records[params.domain]; } async getArNSRecords( - params: ReadInteractionFilters, + params: EvaluationOptions, ): Promise> { - return this.cache.getArNSRecords(params); + const state = await this.contract.getContractState(params); + return state.records; } async getBalance( - params: { address: string } & ReadInteractionFilters, + params: { address: string } & EvaluationOptions, ): Promise { - return this.cache.getBalance(params); + const balances = await this.getBalances(params); + return balances[params.address] || 0; } - async getBalances(): Promise> { - return this.cache.getBalances(); + async getBalances( + params: EvaluationOptions, + ): Promise> { + const state = await this.contract.getContractState(params); + return state.balances; } - async getGateway(params: { address: string }): Promise { - return this.cache.getGateway(params); + async getGateway( + params: { address: string } & EvaluationOptions, + ): Promise { + return this.contract.readInteraction({ + functionName: 'gateway', + inputs: { + target: params.address, + }, + evaluationParameters: params.evaluationParameters, + }); } async getGateways( - params: ReadInteractionFilters, + params: EvaluationOptions, ): Promise> { - return this.cache.getGateways(params); + return this.contract.readInteraction({ + functionName: 'gateways', + evaluationParameters: params.evaluationParameters, + }); + } +} + +export class AntContract implements IAntContract { + private contract: SmartWeaveContract; + + constructor({ contract }: Required) { + this.contract = contract; + } + + async getState(params: EvaluationOptions): Promise { + return this.contract.getContractState(params); + } + + async getRecord( + params: { undername: string } & EvaluationOptions, + ): Promise<{ ttlSeconds: number; transactionId: string }> { + const state = await this.contract.getContractState(params); + return state.records[params.undername]; + } + + async getRecords( + params: EvaluationOptions, + ): Promise> { + const state = await this.contract.getContractState(params); + return state.records; + } + + async getOwner( + params: { domain: string } & EvaluationOptions, + ): Promise { + const state = await this.contract.getContractState(params); + return state.owner; + } + + async getControllers(params: EvaluationOptions): Promise { + const state = await this.contract.getContractState(params); + return state.controllers; } } diff --git a/src/common/caches/arns-remote-cache.ts b/src/common/caches/arns-remote-cache.ts deleted file mode 100644 index 8589aaa6..00000000 --- a/src/common/caches/arns-remote-cache.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (C) 2022-2024 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 { ARNS_TESTNET_REGISTRY_TX, ARWEAVE_TX_REGEX } from '../../constants.js'; -import { - ArIOContract, - ArNSNameData, - ArNSStateResponse, - EvalToParams, - Gateway, - HTTPClient, - ReadInteractionFilters, -} from '../../types/index.js'; -import { isBlockHeight, isSortKey } from '../../utils/index.js'; -import { NotFound } from '../error.js'; -import { AxiosHTTPService } from '../http.js'; -import { DefaultLogger } from '../logger.js'; - -export class ArNSRemoteCache implements ArIOContract { - private contractTxId: string; - private logger: DefaultLogger; - private http: HTTPClient; - private apiVersion = 'v1' as const; // use v1 endpoints - constructor({ - url = 'https://api.arns.app', - logger = new DefaultLogger({ - level: 'debug', - logFormat: 'simple', - }), - contractTxId = ARNS_TESTNET_REGISTRY_TX, - }: { - url?: string; - logger?: DefaultLogger; - contractTxId?: string; - }) { - this.validateContractTxId(contractTxId); - this.contractTxId = contractTxId; - this.logger = logger; - this.http = new AxiosHTTPService({ - url: `${url}/${this.apiVersion}`, - logger, - }); - } - sortKeyOrBlockHeightParams(historicalIndex: any): EvalToParams { - if (isSortKey(historicalIndex?.sortKey)) { - return { sortKey: historicalIndex.sortKey }; - } - if (isBlockHeight(historicalIndex?.blockHeight)) { - return { blockHeight: historicalIndex.blockHeight }; - } - return {}; - } - private validateContractTxId(id: string) { - if (!ARWEAVE_TX_REGEX.test(id)) { - throw new Error(`Invalid contract tx id: ${id}`); - } - } - - async getGateway({ - address, - evaluationParameters, - }: { address: string } & ReadInteractionFilters) { - this.logger.debug(`Fetching gateway ${address}`); - - const gateway = await this.getGateways({ evaluationParameters }).then( - (gateways) => { - if (gateways[address] === undefined) { - throw new NotFound(`Gateway not found: ${address}`); - } - return gateways[address]; - }, - ); - return gateway; - } - - async getGateways({ evaluationParameters }: ReadInteractionFilters = {}) { - this.logger.debug(`Fetching gateways`); - - const params = this.sortKeyOrBlockHeightParams( - evaluationParameters?.evalTo, - ); - - const { result } = await this.http.get< - ArNSStateResponse<'result', Record> - >({ - endpoint: `/contract/${this.contractTxId.toString()}/read/gateways`, - params, - }); - return result; - } - - async getBalance({ - address, - evaluationParameters, - }: { address: string } & ReadInteractionFilters) { - this.logger.debug(`Fetching balance for ${address}`); - - const params = this.sortKeyOrBlockHeightParams( - evaluationParameters?.evalTo, - ); - - const { result } = await this.http - .get>({ - endpoint: `/contract/${this.contractTxId.toString()}/state/balances/${address}`, - params, - }) - .catch((e) => { - if (e instanceof NotFound) { - return { result: 0 }; - } - throw e; - }); - return result; - } - - async getBalances({ evaluationParameters }: ReadInteractionFilters = {}) { - this.logger.debug(`Fetching balances`); - - const params = this.sortKeyOrBlockHeightParams( - evaluationParameters?.evalTo, - ); - - const { result } = await this.http.get< - ArNSStateResponse<'result', Record> - >({ - endpoint: `/contract/${this.contractTxId.toString()}/state/balances`, - params, - }); - return result; - } - - async getArNSRecord({ - domain, - evaluationParameters, - }: { domain: string } & ReadInteractionFilters): Promise { - this.logger.debug(`Fetching record for ${domain}`); - - const params = this.sortKeyOrBlockHeightParams( - evaluationParameters?.evalTo, - ); - - const { result } = await this.http.get< - ArNSStateResponse<'result', ArNSNameData> - >({ - endpoint: `/contract/${this.contractTxId.toString()}/state/records/${domain}`, - params, - }); - return result; - } - - async getArNSRecords({ - evaluationParameters, - }: ReadInteractionFilters = {}): Promise> { - this.logger.debug(`Fetching all records`); - - const params = this.sortKeyOrBlockHeightParams( - evaluationParameters?.evalTo, - ); - - const { result } = await this.http.get< - ArNSStateResponse<'result', Record> - >({ - endpoint: `/contract/${this.contractTxId.toString()}/state/records`, - params, - }); - return result; - } -} diff --git a/src/common/caches/remote-contract.ts b/src/common/caches/remote-contract.ts new file mode 100644 index 00000000..93b3d35f --- /dev/null +++ b/src/common/caches/remote-contract.ts @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2022-2024 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 { + EvaluationParameters, + HTTPClient, + Logger, + SmartWeaveContract, +} from '../../types/index.js'; +import { AxiosHTTPService } from '../http.js'; +import { DefaultLogger } from '../logger.js'; + +export class ArIORemoteContract implements SmartWeaveContract { + private logger: Logger; + private http: HTTPClient; + private contractTxId: string; + + constructor({ + url = 'https://api.arns.app', + contractTxId, + logger = new DefaultLogger({ + level: 'debug', + logFormat: 'simple', + }), + }: { + contractTxId: string; + url?: string; + logger?: DefaultLogger; + }) { + this.logger = logger; + this.http = new AxiosHTTPService({ + url: `${url}/v1`, + logger, + }); + this.contractTxId = contractTxId; + } + + async getContractState({ + evaluationParameters, + }: { + evaluationParameters?: EvaluationParameters; + }): Promise { + this.logger.debug(`Fetching contract state`, { + contractTxId: this.contractTxId, + evaluationParameters, + }); + return this.http.get({ + endpoint: `/contract/${this.contractTxId}/state`, + params: evaluationParameters?.evalTo, + }); + } + + async readInteraction({ + functionName, + inputs, + evaluationParameters, + }: { + functionName: string; + inputs: object; + evaluationParameters: EvaluationParameters; + }): Promise { + this.logger.debug(`Evaluating read interaction on contract`, { + functionName, + inputs, + evaluationParameters, + }); + return this.http.get({ + endpoint: `/contract/${this.contractTxId}/read/${functionName}`, + params: { + ...evaluationParameters.evalTo, + ...inputs, + }, + }); + } +} diff --git a/src/common/caches/warp-contract.ts b/src/common/caches/warp-contract.ts new file mode 100644 index 00000000..6695b3a1 --- /dev/null +++ b/src/common/caches/warp-contract.ts @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2022-2024 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 { + Contract, + Warp, + WarpFactory, + defaultCacheOptions, +} from 'warp-contracts/web'; + +import { SmartWeaveContract } from '../../types/index.js'; + +export const defaultWarpClient = WarpFactory.forMainnet({ + ...defaultCacheOptions, + inMemory: true, // default to in memory for now, a custom warp implementation can be provided +}); + +export class WarpContract implements SmartWeaveContract { + private contract: Contract; + private contractTxId: string; + private cacheUrl: string | undefined; + + constructor({ + contractTxId, + cacheUrl, + warp = defaultWarpClient, + }: { + cacheUrl?: string; + warp: Warp; + contractTxId: string; + }) { + // sync state + this.contract = warp.contract(contractTxId); + this.cacheUrl = cacheUrl; + } + + private async syncState() { + if (this.cacheUrl !== undefined) { + await this.contract.syncState( + `${this.cacheUrl}/v1/contract/${this.contractTxId}`, + { + validity: true, + }, + ); + } + } + + async getContractState(): Promise { + await this.syncState(); + const evaluationResult = await this.contract.readState(); + if (!evaluationResult.cachedValue.state) { + throw new Error('Contract state is not available'); + } + return evaluationResult.cachedValue.state; + } + + async readInteraction({ + functionName, + inputs, + }: { + functionName: string; + inputs: object; + }): Promise { + const evaluationResult = await this.contract.viewState({ + functionName, + ...inputs, + }); + if (!evaluationResult.result) { + throw new Error('Contract state is not available'); + } + return evaluationResult.result; + } +} diff --git a/src/common/http.ts b/src/common/http.ts index 30ee90c0..1b22f463 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -69,39 +69,4 @@ export class AxiosHTTPService implements HTTPClient { return data; } - - // async post({ - // endpoint, - // signal, - // allowedStatuses = [200, 202], - // headers, - // data, - // }: { - // endpoint: string; - // signal?: AbortSignal; - // allowedStatuses?: number[]; - // headers?: Record; - // data: Readable | Buffer | ReadableStream; - // }): Promise { - // const { - // status, - // statusText, - // data: response, - // } = await this.axios.post(endpoint, data, { - // headers, - // signal, - // }); - - // if (!allowedStatuses.includes(status)) { - // switch (status) { - // case 404: - // throw new NotFound(statusText); - // case 400: - // throw new FailedRequestError(status, statusText); - // default: - // throw new UnknownError(statusText); - // } - // } - // return response; - // } } diff --git a/src/types/common.ts b/src/types/common.ts index 7529e45e..092877bc 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -14,46 +14,73 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { ArNSNameData, Gateway } from './contract-state.js'; +import { + AntRecord, + AntState, + ArIOState, + ArNSNameData, + Gateway, +} from './contract-state.js'; export type BlockHeight = number; export type SortKey = string; +export type WalletAddress = string; export type EvalToParams = - | { sortKey?: SortKey } - | { blockHeight?: BlockHeight }; + | { sortKey: SortKey } + | { blockHeight: BlockHeight } + | undefined; export type EvaluationParameters = { evalTo?: EvalToParams; }; // TODO: extend type with other read filters (e.g max eval time) -export type ReadInteractionFilters = { +export type EvaluationOptions = { evaluationParameters?: EvaluationParameters; }; +export interface SmartWeaveContract { + getContractState(params: EvaluationOptions): Promise; + readInteraction( + params: { functionName: string; inputs?: unknown } & EvaluationOptions, + ): Promise; + // TODO: write interaction +} + // TODO: extend with additional methods export interface ArIOContract { + getState(params: EvaluationOptions): Promise; getGateway( - params: { address: WalletAddress } & ReadInteractionFilters, + params: { address: WalletAddress } & EvaluationOptions, ): Promise; getGateways( - params?: ReadInteractionFilters, + params?: EvaluationOptions, ): Promise>; getBalance( - params: { address: WalletAddress } & ReadInteractionFilters, + params: { address: WalletAddress } & EvaluationOptions, ): Promise; getBalances( - params?: ReadInteractionFilters, + params?: EvaluationOptions, ): Promise>; getArNSRecord( - params: { domain: string } & ReadInteractionFilters, + params: { domain: string } & EvaluationOptions, ): Promise; getArNSRecords( - params?: ReadInteractionFilters, + params?: EvaluationOptions, ): Promise>; } +export interface AntContract { + getState(params: EvaluationOptions): Promise; + getRecords(params: EvaluationOptions): Promise>; + getRecord( + params: { undername: string } & EvaluationOptions, + ): Promise; + getOwner(params: EvaluationOptions): Promise; + getControllers(params: EvaluationOptions): Promise; +} + /* eslint-disable @typescript-eslint/no-explicit-any */ export interface Logger { setLogLevel: (level: string) => void; @@ -65,8 +92,6 @@ export interface Logger { } /* eslint-enable @typescript-eslint/no-explicit-any */ -export type WalletAddress = string; - export interface HTTPClient { get({ endpoint, @@ -81,18 +106,4 @@ export interface HTTPClient { allowedStatuses?: number[]; params?: Record; }): Promise; - // TODO: add post method - // post({ - // endpoint, - // signal, - // headers, - // allowedStatuses, - // data, - // }: { - // endpoint: string; - // signal: AbortSignal; - // headers?: Record; - // allowedStatuses?: number[]; - // data: Readable | ReadableStream | Buffer; - // }): Promise; } diff --git a/src/types/contract-state.ts b/src/types/contract-state.ts index ce8c3ed2..722e71c8 100644 --- a/src/types/contract-state.ts +++ b/src/types/contract-state.ts @@ -165,7 +165,7 @@ export type RegistryVaults = Record; export type PrescribedObservers = Record; -export interface IOState { +export interface ArIOState { balances: Balances; name: string; // The friendly name of the token, shown in block explorers and marketplaces records: Record; // The list of all ArNS names and their associated data @@ -180,3 +180,17 @@ export interface IOState { vaults: RegistryVaults; prescribedObservers: PrescribedObservers; } + +export type AntRecord = { + ttlSeconds: number; + transactionId: string; +}; + +export interface AntState { + balances: Balances; + name: string; + ticket: string; + records: Record; + owner: string; + controllers: string[]; +} From 6eb7ef5ed7f0ec3e64e0c18e97a708d7c528ff21 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 5 Mar 2024 18:39:53 -0700 Subject: [PATCH 10/18] feat(ant): create ant contract class for interacting with ant contracts --- src/common/ar-io.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 7489a273..f56287be 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -32,7 +32,7 @@ export type ContractConfiguration = { }; export class ArIO implements ArIOContract { - private contract: SmartWeaveContract; // TODO: this could just be SmartWeaveContract but with the generic ensures any custom setup requires the user to pass in the correct contract type + private contract: SmartWeaveContract; // TODO: this could just be scoped to WarpContract | ArIORemoteContract constructor({ contract = new ArIORemoteContract({ @@ -95,7 +95,9 @@ export class ArIO implements ArIOContract { export class AntContract implements ArNSAntContract { private contract: SmartWeaveContract; - constructor({ contract }: Required) { + constructor({ contractTxId, contract = new ArIORemoteContract({ + contractTxId, + }) }: { contractTxId: string } & ContractConfiguration) { this.contract = contract; } From a58e5b41777e0518abe670a499a4d1455892cae8 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 5 Mar 2024 18:41:13 -0700 Subject: [PATCH 11/18] chore: make husky files executable --- .husky/commit-msg | 0 .husky/pre-commit | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .husky/commit-msg mode change 100644 => 100755 .husky/pre-commit diff --git a/.husky/commit-msg b/.husky/commit-msg old mode 100644 new mode 100755 diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 From 8119e5e2779de07ce7a4fe74abd4e4b8b10e7b32 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Tue, 5 Mar 2024 18:44:10 -0700 Subject: [PATCH 12/18] chore: remove ant contract implementations for now We will come back to these. --- README.md | 4 +--- src/common/ar-io.ts | 46 ++----------------------------------- src/types/contract-state.ts | 14 ----------- 3 files changed, 3 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index a3899006..c03720c5 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Alternatively - you can choose to evaluate contracts locally leveraging [Warp]. // setup a local contract and evaluate it using warp const localContract = new WarpContract({ contractTxId: 'TESTNET_CONTRACT_TX_ID', -}) +}); const localArIO = new ArIO({ contract: localContract, @@ -192,7 +192,6 @@ Retrieves the balances of the ArIO contract. --> ```typescript - const arIO = new ArIO(); const balances = arIO.getBalances(); @@ -249,7 +248,6 @@ const gateway = arIO.getGateway({ // "normalizedCompositeWeight": 0.27485583057217183 // } // } - ``` #### `getGateways()` diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index f56287be..5c8ff905 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -16,16 +16,14 @@ */ import { ARNS_TESTNET_REGISTRY_TX } from '../constants.js'; import { - AntState, ArIOContract, ArIOState, - ArNSAntContract, ArNSNameData, EvaluationOptions, Gateway, SmartWeaveContract, } from '../types/index.js'; -import { ArIORemoteContract, WarpContract } from './index.js'; +import { ArIORemoteContract } from './index.js'; export type ContractConfiguration = { contract?: SmartWeaveContract; @@ -39,7 +37,7 @@ export class ArIO implements ArIOContract { contractTxId: ARNS_TESTNET_REGISTRY_TX, }), }: { - contract?: SmartWeaveContract + contract?: SmartWeaveContract; }) { this.contract = contract; } @@ -91,43 +89,3 @@ export class ArIO implements ArIOContract { }); } } - -export class AntContract implements ArNSAntContract { - private contract: SmartWeaveContract; - - constructor({ contractTxId, contract = new ArIORemoteContract({ - contractTxId, - }) }: { contractTxId: string } & ContractConfiguration) { - this.contract = contract; - } - - async getState(params: EvaluationOptions): Promise { - return this.contract.getContractState(params); - } - - async getRecord( - params: { undername: string } & EvaluationOptions, - ): Promise<{ ttlSeconds: number; transactionId: string }> { - const state = await this.contract.getContractState(params); - return state.records[params.undername]; - } - - async getRecords( - params: EvaluationOptions, - ): Promise> { - const state = await this.contract.getContractState(params); - return state.records; - } - - async getOwner( - params: { domain: string } & EvaluationOptions, - ): Promise { - const state = await this.contract.getContractState(params); - return state.owner; - } - - async getControllers(params: EvaluationOptions): Promise { - const state = await this.contract.getContractState(params); - return state.controllers; - } -} diff --git a/src/types/contract-state.ts b/src/types/contract-state.ts index 722e71c8..6f5b7c54 100644 --- a/src/types/contract-state.ts +++ b/src/types/contract-state.ts @@ -180,17 +180,3 @@ export interface ArIOState { vaults: RegistryVaults; prescribedObservers: PrescribedObservers; } - -export type AntRecord = { - ttlSeconds: number; - transactionId: string; -}; - -export interface AntState { - balances: Balances; - name: string; - ticket: string; - records: Record; - owner: string; - controllers: string[]; -} From a961f6fa8da754e976313610e86dc3b2b7f307a6 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Thu, 7 Mar 2024 09:50:51 -0600 Subject: [PATCH 13/18] chore: remove all ant state implementations --- src/types/common.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/types/common.ts b/src/types/common.ts index a9e83a46..724080ba 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -14,13 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { - AntRecord, - AntState, - ArIOState, - ArNSNameData, - Gateway, -} from './contract-state.js'; +import { ArIOState, ArNSNameData, Gateway } from './contract-state.js'; export type BlockHeight = number; export type SortKey = string; @@ -71,16 +65,6 @@ export interface ArIOContract { ): Promise>; } -export interface ArNSAntContract { - getState(params: EvaluationOptions): Promise; - getRecords(params: EvaluationOptions): Promise>; - getRecord( - params: { undername: string } & EvaluationOptions, - ): Promise; - getOwner(params: EvaluationOptions): Promise; - getControllers(params: EvaluationOptions): Promise; -} - /* eslint-disable @typescript-eslint/no-explicit-any */ export interface Logger { setLogLevel: (level: string) => void; From 8164eef318cceefc1cba0aa2c5e5172cf9910a60 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 8 Mar 2024 08:38:07 -0600 Subject: [PATCH 14/18] chore: update types, interfaces and add docker-compose for tests --- README.md | 34 ++-- docker-compose.yaml | 18 ++ package.json | 3 +- src/common/ar-io.ts | 157 ++++++++++++------ src/common/contracts/remote-contract.ts | 55 +++--- src/common/contracts/warp-contract.ts | 37 +++-- src/common/error.test.ts | 16 -- src/common/http.ts | 19 ++- src/types/common.ts | 81 +++++---- src/utils/http-client.ts | 1 + src/utils/index.ts | 2 +- .../evaluation.ts => smartweave.ts} | 4 +- src/utils/smartweave/evaluation.test.ts | 13 -- src/utils/smartweave/index.ts | 17 -- tests/ar-io.test.ts | 116 ++++++++++++- tests/arns-remote-cache/balances.test.ts | 82 --------- tests/arns-remote-cache/gateways.test.ts | 69 -------- tests/arns-remote-cache/records.test.ts | 99 ----------- {src => tests}/utils/arweave.test.ts | 4 +- tsconfig.json | 2 +- yarn.lock | 22 +-- 21 files changed, 380 insertions(+), 471 deletions(-) create mode 100644 docker-compose.yaml delete mode 100644 src/common/error.test.ts rename src/utils/{smartweave/evaluation.ts => smartweave.ts} (93%) delete mode 100644 src/utils/smartweave/evaluation.test.ts delete mode 100644 src/utils/smartweave/index.ts delete mode 100644 tests/arns-remote-cache/balances.test.ts delete mode 100644 tests/arns-remote-cache/gateways.test.ts delete mode 100644 tests/arns-remote-cache/records.test.ts rename {src => tests}/utils/arweave.test.ts (73%) diff --git a/README.md b/README.md index c03720c5..3a943a51 100644 --- a/README.md +++ b/README.md @@ -137,33 +137,27 @@ Types are exported from `./lib/types/[node/web]/index.d.ts` and should be automa ### Custom Contract Evaluation -```typescript -// use an experimental contract that satisfies the ArIOContract state and evaluate it using a remote contract service -const testnetContract = new ArIORemoteContract({ - contractTxId: 'TESTNET_CONTRACT_TX_ID', - remoteCacheUrl: 'http://localhost:3000', // my local cache service -}); - -const testnet = new ArIO({ - contract: remoteContract, -}); - -const gateways = testnet.getGateways(); // evaluates the ArIO contract using the remote cache service -``` - -Alternatively - you can choose to evaluate contracts locally leveraging [Warp]. +By default - the `ArIO` client uses the `mainnet` contract and exposes APIs relevant to the `ArIO` contract. You can provide custom `contract` or `contractTxId` to the `ArIO` constructor and expose those APIs, assuming the contract is compatible with the `ArIO` contract. ```typescript -// setup a local contract and evaluate it using warp -const localContract = new WarpContract({ +// provide a custom contractTxId to the client and default to remote evaluation +const remoteCustomArIO = new ArIO({ contractTxId: 'TESTNET_CONTRACT_TX_ID', }); -const localArIO = new ArIO({ - contract: localContract, +// provide a custom contract to the client, and specify local evaluation using warp +const localCustomArIO = new ArIO({ + contract: new WarpContract({ + contractTxId: 'TESTNET_CONTRACT_TX_ID', + }) }); -const gateways = localArIO.getGateways(); // evaluates the ArIO contract locally using warp +// provide a custom contract to the client, and specify local evaluation using remote cache +const remoteCacheCustomArIO = new ArIO({ + contract: new RemoteContract({ + contractTxId: 'TESTNET_CONTRACT_TX_ID', + }) +}); ``` ## APIs diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..8b0dc80c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,18 @@ +version: '3' + +services: + arns-service: + image: ghcr.io/ar-io/arns-service:latest + build: . + ports: + - "3000:3000" + environment: + - LOG_LEVEL=debug + - PREFETCH_CONTRACTS=true + - PREFETCH_CONTRACT_IDS=_NctcA2sRy1-J4OmIQZbYFPM17piNcbdBPH2ncX2RL8 + - BOOTSTRAP_CONTRACTS=false + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/healthcheck"] + interval: 10s + timeout: 5s + retries: 5 diff --git a/package.json b/package.json index f7a64cec..467e197f 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "format": "prettier --check .", "format:fix": "prettier --write .", "test": "yarn clean && c8 jest .", + "test:integration": "docker compose up -d && yarn test && docker compose down", "prepare": "husky install", "example:mjs": "yarn build:esm && node examples/node/index.mjs", "example:cjs": "yarn build:cjs && node examples/node/index.cjs", @@ -98,7 +99,7 @@ "dependencies": { "arweave": "^1.14.4", "axios": "1.4.0", - "warp-contracts": "1.4.29", + "warp-contracts": "^1.4.37", "winston": "^3.11.0" } } diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 5c8ff905..8356dc67 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -19,73 +19,134 @@ import { ArIOContract, ArIOState, ArNSNameData, - EvaluationOptions, + EvaluationParameters, Gateway, SmartWeaveContract, } from '../types/index.js'; -import { ArIORemoteContract } from './index.js'; +import { RemoteContract } from './index.js'; -export type ContractConfiguration = { - contract?: SmartWeaveContract; -}; +// TODO: append this with other configuration options (e.g. local vs. remote evaluation) +export type ContractConfiguration = + | { + contract?: SmartWeaveContract; + } + | { + contractTxId: string; + }; + +function isContractConfiguration( + config: ContractConfiguration, +): config is { contract: SmartWeaveContract } { + return 'contract' in config; +} + +function isContractTxIdConfiguration( + config: ContractConfiguration, +): config is { contractTxId: string } { + return 'contractTxId' in config; +} export class ArIO implements ArIOContract { - private contract: SmartWeaveContract; // TODO: this could just be scoped to WarpContract | ArIORemoteContract + private contract: SmartWeaveContract; - constructor({ - contract = new ArIORemoteContract({ - contractTxId: ARNS_TESTNET_REGISTRY_TX, - }), - }: { - contract?: SmartWeaveContract; - }) { - this.contract = contract; + constructor( + config: ContractConfiguration = { + // default to a contract that uses the arns service to do the evaluation + contract: new RemoteContract({ + contractTxId: ARNS_TESTNET_REGISTRY_TX, + }), + }, + ) { + if (isContractConfiguration(config)) { + this.contract = config.contract; + } else if (isContractTxIdConfiguration(config)) { + this.contract = new RemoteContract({ + contractTxId: config.contractTxId, + }); + } } - async getState(params: EvaluationOptions): Promise { - return this.contract.getContractState(params); + /** + * Returns the current state of the contract. + */ + async getState(params: EvaluationParameters): Promise { + const state = await this.contract.getContractState(params); + return state; } - async getArNSRecord( - params: { domain: string } & EvaluationOptions, - ): Promise { - const records = await this.getArNSRecords(params); - return records[params.domain]; + + /** + * Returns the ARNS record for the given domain. + */ + async getArNSRecord({ + domain, + evaluationOptions, + }: EvaluationParameters<{ domain: string }>): Promise< + ArNSNameData | undefined + > { + const records = await this.getArNSRecords({ evaluationOptions }); + return records[domain]; } - async getArNSRecords( - params: EvaluationOptions, - ): Promise> { - const state = await this.contract.getContractState(params); + + /** + * Returns all ArNS records. + */ + async getArNSRecords({ + evaluationOptions, + }: EvaluationParameters = {}): Promise> { + const state = await this.contract.getContractState({ evaluationOptions }); return state.records; } - async getBalance( - params: { address: string } & EvaluationOptions, - ): Promise { - const balances = await this.getBalances(params); - return balances[params.address] || 0; + + /** + * Returns the balance of the given address. + */ + async getBalance({ + address, + evaluationOptions, + }: EvaluationParameters<{ address: string }>): Promise { + const balances = await this.getBalances({ evaluationOptions }); + return balances[address] || 0; } - async getBalances( - params: EvaluationOptions, - ): Promise> { - const state = await this.contract.getContractState(params); + + /** + * Returns the balances of all addresses. + */ + async getBalances({ evaluationOptions }: EvaluationParameters = {}): Promise< + Record + > { + const state = await this.contract.getContractState({ evaluationOptions }); return state.balances; } - async getGateway( - params: { address: string } & EvaluationOptions, - ): Promise { - return this.contract.readInteraction({ - functionName: 'gateway', - inputs: { - target: params.address, - }, - evaluationParameters: params.evaluationParameters, - }); + + /** + * Returns the gateway for the given address, including weights. + */ + async getGateway({ + address, + evaluationOptions, + }: EvaluationParameters<{ address: string }>): Promise { + return this.contract + .readInteraction<{ target: string }, Gateway>({ + functionName: 'gateway', + inputs: { + target: address, + }, + evaluationOptions, + }) + .catch(() => { + return undefined; + }); } - async getGateways( - params: EvaluationOptions, - ): Promise> { + + /** + * Returns all gateways, including weights. + */ + async getGateways({ evaluationOptions }: EvaluationParameters = {}): Promise< + Record | Record + > { return this.contract.readInteraction({ functionName: 'gateways', - evaluationParameters: params.evaluationParameters, + evaluationOptions, }); } } diff --git a/src/common/contracts/remote-contract.ts b/src/common/contracts/remote-contract.ts index 93b3d35f..c2c27549 100644 --- a/src/common/contracts/remote-contract.ts +++ b/src/common/contracts/remote-contract.ts @@ -23,7 +23,8 @@ import { import { AxiosHTTPService } from '../http.js'; import { DefaultLogger } from '../logger.js'; -export class ArIORemoteContract implements SmartWeaveContract { +// TODO: this assumes the API structure matches the current arns-service API - we will want to consider another interface that exposes relevant APIs with client implementations (arns-service, DRE nodes, etc.) +export class RemoteContract implements SmartWeaveContract { private logger: Logger; private http: HTTPClient; private contractTxId: string; @@ -31,58 +32,58 @@ export class ArIORemoteContract implements SmartWeaveContract { constructor({ url = 'https://api.arns.app', contractTxId, - logger = new DefaultLogger({ - level: 'debug', - logFormat: 'simple', - }), + logger = new DefaultLogger(), }: { contractTxId: string; url?: string; logger?: DefaultLogger; }) { + this.contractTxId = contractTxId; this.logger = logger; this.http = new AxiosHTTPService({ - url: `${url}/v1`, - logger, + url: `${url}/v1/contract/${contractTxId}`, }); - this.contractTxId = contractTxId; } async getContractState({ - evaluationParameters, - }: { - evaluationParameters?: EvaluationParameters; - }): Promise { + evaluationOptions, + }: EvaluationParameters = {}): Promise { this.logger.debug(`Fetching contract state`, { contractTxId: this.contractTxId, - evaluationParameters, + evaluationOptions, }); - return this.http.get({ - endpoint: `/contract/${this.contractTxId}/state`, - params: evaluationParameters?.evalTo, + const { state } = await this.http.get< + { sortKey: string } | { blockHeight: number } | Record, + { state: T } + >({ + endpoint: ``, + params: { + ...evaluationOptions?.evalTo, + }, }); + return state; } - async readInteraction({ + async readInteraction({ functionName, inputs, - evaluationParameters, - }: { - functionName: string; - inputs: object; - evaluationParameters: EvaluationParameters; - }): Promise { + evaluationOptions, + }: EvaluationParameters<{ functionName: string; inputs?: I }>): Promise { this.logger.debug(`Evaluating read interaction on contract`, { functionName, inputs, - evaluationParameters, + evaluationOptions, }); - return this.http.get({ - endpoint: `/contract/${this.contractTxId}/read/${functionName}`, + const { result } = await this.http.get< + I | Record, + { result: K } + >({ + endpoint: `/read/${functionName}`, params: { - ...evaluationParameters.evalTo, + ...evaluationOptions?.evalTo, ...inputs, }, }); + return result; } } diff --git a/src/common/contracts/warp-contract.ts b/src/common/contracts/warp-contract.ts index 4b1569fd..2fbce91c 100644 --- a/src/common/contracts/warp-contract.ts +++ b/src/common/contracts/warp-contract.ts @@ -21,14 +21,15 @@ import { defaultCacheOptions, } from 'warp-contracts'; -import { SmartWeaveContract } from '../../types/index.js'; +import { EvaluationParameters, SmartWeaveContract } from '../../types/index.js'; +import { FailedRequestError } from '../error.js'; export const defaultWarpClient = WarpFactory.forMainnet({ ...defaultCacheOptions, inMemory: true, // default to in memory for now, a custom warp implementation can be provided }); -export class WarpContract implements SmartWeaveContract { +export class WarpContract implements SmartWeaveContract { private contract: Contract; private contractTxId: string; private cacheUrl: string | undefined; @@ -58,28 +59,40 @@ export class WarpContract implements SmartWeaveContract { } } - async getContractState(): Promise { + async getContractState({ + evaluationOptions = {}, + }: EvaluationParameters): Promise { await this.syncState(); - const evaluationResult = await this.contract.readState(); + const evalTo = evaluationOptions?.evalTo; + let sortKeyOrBlockHeight: string | number | undefined; + if (evalTo && 'sortKey' in evalTo) { + sortKeyOrBlockHeight = evalTo.sortKey; + } else if (evalTo && 'blockHeight') { + sortKeyOrBlockHeight = evalTo.blockHeight; + } + + const evaluationResult = + await this.contract.readState(sortKeyOrBlockHeight); if (!evaluationResult.cachedValue.state) { - throw new Error('Contract state is not available'); + throw new FailedRequestError(502, 'Failed to evaluate contract state'); } - return evaluationResult.cachedValue.state; + return evaluationResult.cachedValue.state as T; } - async readInteraction({ + async readInteraction({ functionName, inputs, - }: { - functionName: string; - inputs: object; - }): Promise { + // TODO: view state only supports sort key so we won't be able to use block height + }: EvaluationParameters<{ functionName: string; inputs: I }>): Promise { const evaluationResult = await this.contract.viewState({ functionName, ...inputs, }); if (!evaluationResult.result) { - throw new Error('Contract state is not available'); + throw new FailedRequestError( + 502, + 'Failed to evaluate contract read interaction', + ); } return evaluationResult.result; } diff --git a/src/common/error.test.ts b/src/common/error.test.ts deleted file mode 100644 index 068838e7..00000000 --- a/src/common/error.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BadRequest, NotFound } from './error.js'; - -describe('Error', () => { - it.each([ - ['BadRequest', BadRequest], - ['NotFound', NotFound], - ])( - 'Errors should inherit Base error and names should be applied appropriately', - (name, errorClass) => { - const message = 'This is a test error'; - const error = new errorClass(message); - expect(error.name).toEqual(name); - expect(error.message).toEqual(message); - }, - ); -}); diff --git a/src/common/http.ts b/src/common/http.ts index 1b22f463..7e566f0c 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -19,22 +19,28 @@ import { AxiosInstance } from 'axios'; import { HTTPClient, Logger } from '../types/index.js'; import { createAxiosInstance } from '../utils/index.js'; import { FailedRequestError, NotFound, UnknownError } from './error.js'; +import { DefaultLogger } from './logger.js'; export class AxiosHTTPService implements HTTPClient { private axios: AxiosInstance; private logger: Logger; // TODO: re-implement axios-retry. Currently that package is broken for nodenext. - constructor({ url, logger }: { url: string; logger: Logger }) { + constructor({ + url, + logger = new DefaultLogger(), + }: { + url: string; + logger?: Logger; + }) { this.logger = logger; this.axios = createAxiosInstance({ axiosConfig: { baseURL: url, - maxRedirects: 0, }, }); } - async get({ + async get({ endpoint, signal, allowedStatuses = [200, 202], @@ -45,17 +51,16 @@ export class AxiosHTTPService implements HTTPClient { signal?: AbortSignal; allowedStatuses?: number[]; headers?: Record; - params?: Record; - }): Promise { + params?: I; + }): Promise { this.logger.debug( `Get request to endpoint: ${endpoint} with params ${JSON.stringify(params, undefined, 2)}`, ); - const { status, statusText, data } = await this.axios.get(endpoint, { + const { status, statusText, data } = await this.axios.get(endpoint, { headers, signal, params, }); - if (!allowedStatuses.includes(status)) { switch (status) { case 404: diff --git a/src/types/common.ts b/src/types/common.ts index 724080ba..4bde8de5 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -20,49 +20,59 @@ export type BlockHeight = number; export type SortKey = string; export type WalletAddress = string; -export type EvalToParams = - | { sortKey: SortKey } - | { blockHeight: BlockHeight } - | undefined; - -export type EvaluationParameters = { - evalTo?: EvalToParams; -}; - -// TODO: extend type with other read filters (e.g max eval time) export type EvaluationOptions = { - evaluationParameters?: EvaluationParameters; + evalTo?: { sortKey: SortKey } | { blockHeight: BlockHeight }; + // TODO: any other evaluation constraints }; -export interface SmartWeaveContract { - getContractState(params: EvaluationOptions): Promise; - readInteraction( - params: { functionName: string; inputs?: unknown } & EvaluationOptions, - ): Promise; +// combine evaluation parameters with read interaction inputs +export type EvaluationParameters> = { + evaluationOptions?: EvaluationOptions | Record | undefined; +} & T; + +export interface SmartWeaveContract { + getContractState(params: EvaluationParameters): Promise; + readInteraction({ + functionName, + inputs, + evaluationOptions, + }: EvaluationParameters<{ functionName: string; inputs?: I }>): Promise; // TODO: write interaction } // TODO: extend with additional methods export interface ArIOContract { - getState(params: EvaluationOptions): Promise; - getGateway( - params: { address: WalletAddress } & EvaluationOptions, - ): Promise; - getGateways( - params?: EvaluationOptions, - ): Promise>; + getState({ evaluationOptions }: EvaluationParameters): Promise; + getGateway({ + address, + evaluationOptions, + }: EvaluationParameters<{ address: WalletAddress }>): Promise< + Gateway | undefined + >; + getGateways({ + evaluationOptions, + }: EvaluationParameters): Promise< + Record | Record + >; getBalance( params: { address: WalletAddress } & EvaluationOptions, ): Promise; - getBalances( - params?: EvaluationOptions, - ): Promise>; - getArNSRecord( - params: { domain: string } & EvaluationOptions, - ): Promise; - getArNSRecords( - params?: EvaluationOptions, - ): Promise>; + getBalances({ + evaluationOptions, + }: EvaluationParameters): Promise< + Record | Record + >; + getArNSRecord({ + domain, + evaluationOptions, + }: EvaluationParameters<{ domain: string }>): Promise< + ArNSNameData | undefined + >; + getArNSRecords({ + evaluationOptions, + }: EvaluationParameters): Promise< + Record | Record + >; } /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -75,9 +85,8 @@ export interface Logger { debug: (message: string, ...args: any[]) => void; } /* eslint-enable @typescript-eslint/no-explicit-any */ - export interface HTTPClient { - get({ + get({ endpoint, signal, headers, @@ -88,6 +97,6 @@ export interface HTTPClient { signal?: AbortSignal; headers?: Record; allowedStatuses?: number[]; - params?: Record; - }): Promise; + params?: object | I; + }): Promise; } diff --git a/src/utils/http-client.ts b/src/utils/http-client.ts index 4513e352..0da741a8 100644 --- a/src/utils/http-client.ts +++ b/src/utils/http-client.ts @@ -28,6 +28,7 @@ export const createAxiosInstance = ({ }: AxiosInstanceParameters = {}): AxiosInstance => { const axiosInstance = axios.create({ ...axiosConfig, + maxRedirects: 0, headers: { ...axiosConfig.headers, 'x-source-version': `${version}`, diff --git a/src/utils/index.ts b/src/utils/index.ts index 521e4f01..0738d3ef 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -16,4 +16,4 @@ */ export * from './arweave.js'; export * from './http-client.js'; -export * from './smartweave/index.js'; +export * from './smartweave.js'; diff --git a/src/utils/smartweave/evaluation.ts b/src/utils/smartweave.ts similarity index 93% rename from src/utils/smartweave/evaluation.ts rename to src/utils/smartweave.ts index 4d1deddc..c13fb4ac 100644 --- a/src/utils/smartweave/evaluation.ts +++ b/src/utils/smartweave.ts @@ -14,8 +14,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { SORT_KEY_REGEX } from '../../constants.js'; -import { SortKey } from '../../types/common.js'; +import { SORT_KEY_REGEX } from '../constants.js'; +import { SortKey } from '../types/common.js'; export function isSortKey(sortKey: string): sortKey is SortKey { return SmartWeaveSortKey.validate(sortKey); diff --git a/src/utils/smartweave/evaluation.test.ts b/src/utils/smartweave/evaluation.test.ts deleted file mode 100644 index dd8de782..00000000 --- a/src/utils/smartweave/evaluation.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SmartWeaveSortKey } from './evaluation.js'; - -describe(`Smartweave eval utils`, () => { - it(`should throw on a bad sort key`, async () => { - const sortKey = '123,456,abc'; - const error = await (async () => new SmartWeaveSortKey(sortKey))().catch( - (e) => e, - ); - - expect(error).toBeInstanceOf(Error); - expect(error.message).toContain(sortKey); - }); -}); diff --git a/src/utils/smartweave/index.ts b/src/utils/smartweave/index.ts deleted file mode 100644 index c0451be1..00000000 --- a/src/utils/smartweave/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (C) 2022-2024 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 . - */ -export * from './evaluation.js'; diff --git a/tests/ar-io.test.ts b/tests/ar-io.test.ts index b17a45c7..26bfce4d 100644 --- a/tests/ar-io.test.ts +++ b/tests/ar-io.test.ts @@ -1,9 +1,121 @@ import { ArIO } from '../src/common/ar-io.js'; +import { RemoteContract } from '../src/common/index.js'; +import { ARNS_DEVNET_REGISTRY_TX } from '../src/constants.js'; +import { ArIOState } from '../src/types/contract-state.js'; +import { SmartWeaveSortKey } from '../src/utils/smartweave.js'; +const gatewayAddress = '1H7WZIWhzwTH9FIcnuMqYkTsoyv1OTfGa_amvuYwrgo'; +const domain = 'ar-io'; +const evaluateToBlockHeight = 1377100; +const evaluateToSortKey = new SmartWeaveSortKey( + '000001376946,0000000000000,18d52956c8e13ae1f557b4e67f6f298b8ffd2a5cd96e42ec24ca649b7401510f', +); describe('ArIO Client', () => { + const arioClient = new ArIO({ + contract: new RemoteContract({ + url: process.env.REMOTE_CACHE_URL || 'http://localhost:3000', + contractTxId: ARNS_DEVNET_REGISTRY_TX + }), + }); it('should create a custom ArIO client', () => { - const arioClient = new ArIO(); - expect(arioClient).toBeInstanceOf(ArIO); }); + + it('should should return undefined for non existent gateway', async () => { + const nonExistent = await arioClient.getGateway({ address: 'some-address' }) + expect(nonExistent).toEqual(undefined) + }); + + it('should return gateway state at a given block height', async () => { + const gateway = await arioClient.getGateway({ + address: gatewayAddress, + evaluationOptions: { evalTo: { blockHeight: evaluateToBlockHeight } }, + }); + expect(gateway).toBeDefined(); + }); + + it('should return gateway state at a given sort key', async () => { + const gateway = await arioClient.getGateway({ + address: gatewayAddress, + evaluationOptions: { evalTo: { sortKey: evaluateToSortKey.toString() } }, + }); + expect(gateway).toBeDefined(); + }); + + it('should return gateways state at a given block height', async () => { + const gateways = await arioClient.getGateways({ + evaluationOptions: { evalTo: { blockHeight: evaluateToBlockHeight } }, + }); + expect(gateways[gatewayAddress]).toBeDefined(); + }); + + it('should return gateways state at a given sort key', async () => { + const gateways = await arioClient.getGateways({ + evaluationOptions: { evalTo: { sortKey: evaluateToSortKey.toString() } }, + }); + expect(gateways[gatewayAddress]).toBeDefined(); + }); + + it('should fetch a record', async () => { + const record = await arioClient.getArNSRecord({ domain: 'ar-io' }); + expect(record).toBeDefined(); + }); + + it('should throw NotFound error on non existent record', async () => { + const nonExistent = await arioClient.getArNSRecord({ domain: 'some-domain' }) + expect(nonExistent).toEqual(undefined) + }); + + it('should fetch all records', async () => { + const records = await arioClient.getArNSRecords(); + expect(records).toBeDefined(); + }); + + it('should return record at a given block height', async () => { + const currentRecord = await arioClient.getArNSRecord({ + domain, + evaluationOptions: { + evalTo: { blockHeight: evaluateToBlockHeight + 1 }, + }, + }); + expect(currentRecord).toBeDefined(); + + const nonExistent = await arioClient + .getArNSRecord({ + domain, + evaluationOptions: { + evalTo: { blockHeight: 0 }, + }, + }) + + expect(nonExistent).toEqual(undefined) + }); + + it('should return record at a given sort key', async () => { + const record = await arioClient.getArNSRecord({ + domain, + evaluationOptions: { + evalTo: { sortKey: evaluateToSortKey.toString() }, + }, + }); + expect(record).toBeDefined(); + }); + + it('should return records at a given block height', async () => { + const records = await arioClient.getArNSRecords({ + evaluationOptions: { + evalTo: { blockHeight: evaluateToBlockHeight }, + }, + }); + expect(records[domain]).toBeDefined(); + }); + + it('should return records at a given sort key', async () => { + const records = await arioClient.getArNSRecords({ + evaluationOptions: { + evalTo: { sortKey: evaluateToSortKey.toString() }, + }, + }); + expect(records[domain]).toBeDefined(); + }); }); diff --git a/tests/arns-remote-cache/balances.test.ts b/tests/arns-remote-cache/balances.test.ts deleted file mode 100644 index 253fafff..00000000 --- a/tests/arns-remote-cache/balances.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; -import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; -import { SmartWeaveSortKey } from '../../src/utils/index.js'; - -describe('ArNSRemoteCache ~ BALANCES', () => { - const remoteCacheProvider = new ArNSRemoteCache({ - contractTxId: ARNS_DEVNET_REGISTRY_TX, - }); - // balance tests - it('should fetch a balance', async () => { - const balance = await remoteCacheProvider.getBalance({ - address: 'some-address', - }); - expect(balance).toEqual(0); - }); - - it('should fetch all balances', async () => { - const balances = await remoteCacheProvider.getBalances(); - expect(balances).toBeDefined(); - }); - - it('should return balance at a given block height', async () => { - const address = 'ySqMsg7O0R-BcUw35R3nxJJKJyIdauLCQ4DUZqPCiYo'; - const transferBlockHeight = 1364752; - const currentBalance = await remoteCacheProvider.getBalance({ - address, - evaluationParameters: { - evalTo: { blockHeight: transferBlockHeight }, - }, - }); - const transferAmount = 20000; - - const balance = await remoteCacheProvider.getBalance({ - address, - evaluationParameters: { evalTo: { blockHeight: transferBlockHeight } }, - }); - expect(balance).toEqual(currentBalance); - - const previousBalance = await remoteCacheProvider.getBalance({ - address, - evaluationParameters: { - evalTo: { blockHeight: transferBlockHeight - 1 }, - }, - }); - expect(previousBalance).toEqual(currentBalance - transferAmount); - }); - - it('should return balance at a given sort key', async () => { - const address = 'ySqMsg7O0R-BcUw35R3nxJJKJyIdauLCQ4DUZqPCiYo'; - const balanceSortKey = new SmartWeaveSortKey( - '000001364752,0000000000000,7fee05ef004191b252b073628013f987033513c51116d283dc24c866b5c32d0a', - ); - const balance = await remoteCacheProvider.getBalance({ - address, - evaluationParameters: { evalTo: { sortKey: balanceSortKey.toString() } }, - }); - expect(balance).toEqual(20000); - }); - - it('should return balances at a given block height', async () => { - const address = 'ySqMsg7O0R-BcUw35R3nxJJKJyIdauLCQ4DUZqPCiYo'; - const transferBlockHeight = 1364752; - const currentBalance = await remoteCacheProvider.getBalance({ - address, - evaluationParameters: { - evalTo: { blockHeight: transferBlockHeight }, - }, - }); - const balances = await remoteCacheProvider.getBalances({ - evaluationParameters: { evalTo: { blockHeight: transferBlockHeight } }, - }); - - expect(balances[address]).toEqual(currentBalance); - - const previousBalances = await remoteCacheProvider.getBalances({ - evaluationParameters: { - evalTo: { blockHeight: transferBlockHeight - 1 }, - }, - }); - expect(previousBalances[address]).toEqual(undefined); - }); -}); diff --git a/tests/arns-remote-cache/gateways.test.ts b/tests/arns-remote-cache/gateways.test.ts deleted file mode 100644 index 9a0fb742..00000000 --- a/tests/arns-remote-cache/gateways.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; -import { NotFound } from '../../src/common/error.js'; -import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; -import { SmartWeaveSortKey } from '../../src/utils/index.js'; - -describe('ArNSRemoteCache ~ GATEWAYS', () => { - const remoteCacheProvider = new ArNSRemoteCache({ - contractTxId: ARNS_DEVNET_REGISTRY_TX, - }); - const address = '1H7WZIWhzwTH9FIcnuMqYkTsoyv1OTfGa_amvuYwrgo'; - const currentBlockHeight = 1377100; - const previousBlockHeight = currentBlockHeight + 100; - const currentSortKey = new SmartWeaveSortKey( - '000001376946,0000000000000,18d52956c8e13ae1f557b4e67f6f298b8ffd2a5cd96e42ec24ca649b7401510f', - ); - - // gateway tests - it('should be able to fetch gateways', async () => { - const gateways = await remoteCacheProvider.getGateways(); - expect(gateways).toBeDefined(); - }); - - it('should should throw NotFound error on non existent gateway', async () => { - const error = await remoteCacheProvider - .getGateway({ - address: 'some-address', - }) - .catch((e) => e); - expect(error).toBeInstanceOf(NotFound); - }); - - it('should return gateway state at a given block height', async () => { - const gateway = await remoteCacheProvider.getGateway({ - address, - evaluationParameters: { evalTo: { blockHeight: currentBlockHeight } }, - }); - - const previousGatewayState = await remoteCacheProvider.getGateway({ - address, - evaluationParameters: { evalTo: { blockHeight: previousBlockHeight } }, - }); - expect( - previousGatewayState.weights.tenureWeight === - gateway.weights.tenureWeight, - ).toBe(false); - }); - - it('should return gateway state at a given sort key', async () => { - const gateway = await remoteCacheProvider.getGateway({ - address, - evaluationParameters: { evalTo: { sortKey: currentSortKey.toString() } }, - }); - expect(gateway).toBeDefined(); - }); - - it('should return gateways state at a given block height', async () => { - const gateways = await remoteCacheProvider.getGateways({ - evaluationParameters: { evalTo: { blockHeight: currentBlockHeight } }, - }); - expect(gateways[address]).toBeDefined(); - }); - - it('should return gateways state at a given sort key', async () => { - const gateways = await remoteCacheProvider.getGateways({ - evaluationParameters: { evalTo: { sortKey: currentSortKey.toString() } }, - }); - expect(gateways[address]).toBeDefined(); - }); -}); diff --git a/tests/arns-remote-cache/records.test.ts b/tests/arns-remote-cache/records.test.ts deleted file mode 100644 index d093e7d5..00000000 --- a/tests/arns-remote-cache/records.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ArNSRemoteCache } from '../../src/common/caches/arns-remote-cache.js'; -import { NotFound } from '../../src/common/error.js'; -import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; -import { SmartWeaveSortKey } from '../../src/utils/index.js'; - -describe('ArNSRemoteCache ~ RECORDS', () => { - const remoteCacheProvider = new ArNSRemoteCache({ - contractTxId: ARNS_DEVNET_REGISTRY_TX, - }); - // records tests - it('should fetch a record', async () => { - const record = await remoteCacheProvider.getArNSRecord({ - domain: 'ar-io', - }); - expect(record).toBeDefined(); - }); - - it('should throw NotFound error on non existent record', async () => { - const error = await remoteCacheProvider - .getArNSRecord({ - domain: 'some-domain', - }) - .catch((e) => e); - expect(error).toBeInstanceOf(NotFound); - }); - - it('should fetch all records', async () => { - const records = await remoteCacheProvider.getArNSRecords(); - - expect(records).toBeDefined(); - }); - - it('should return record at a given block height', async () => { - const domain = 'testing5'; - const registrationBlockHeight = 1363242; - const currentRecord = await remoteCacheProvider.getArNSRecord({ - domain, - evaluationParameters: { - evalTo: { blockHeight: registrationBlockHeight + 1 }, - }, - }); - expect(currentRecord).toBeDefined(); - - const error = await remoteCacheProvider - .getArNSRecord({ - domain, - evaluationParameters: { - evalTo: { blockHeight: registrationBlockHeight - 1 }, - }, - }) - .catch((e) => e); - expect(error).toBeInstanceOf(NotFound); - }); - - it('should return record at a given sort key', async () => { - const domain = 'testing5'; - const registrationSortKey = new SmartWeaveSortKey( - '000001363242,0000000000000,e7ac482567afa26cf205b158af46bf99f12b1dea0c1dd00caf9a573c8e648430', - ); - const record = await remoteCacheProvider.getArNSRecord({ - domain, - evaluationParameters: { - evalTo: { sortKey: registrationSortKey.toString() }, - }, - }); - expect(record).toBeDefined(); - }); - - it('should return records at a given block height', async () => { - const domain = 'testing5'; - const registrationBlockHeight = 1363242; - const currentRecords = await remoteCacheProvider.getArNSRecords({ - evaluationParameters: { - evalTo: { blockHeight: registrationBlockHeight }, - }, - }); - expect(currentRecords[domain]).toBeDefined(); - - const previousRecords = await remoteCacheProvider.getArNSRecords({ - evaluationParameters: { - evalTo: { blockHeight: registrationBlockHeight - 1 }, - }, - }); - expect(previousRecords[domain]).not.toBeDefined(); - }); - - it('should return records at a given sort key', async () => { - const domain = 'testing5'; - const registrationSortKey = new SmartWeaveSortKey( - '000001363242,0000000000000,e7ac482567afa26cf205b158af46bf99f12b1dea0c1dd00caf9a573c8e648430', - ); - const records = await remoteCacheProvider.getArNSRecords({ - evaluationParameters: { - evalTo: { sortKey: registrationSortKey.toString() }, - }, - }); - expect(records[domain]).toBeDefined(); - }); -}); diff --git a/src/utils/arweave.test.ts b/tests/utils/arweave.test.ts similarity index 73% rename from src/utils/arweave.test.ts rename to tests/utils/arweave.test.ts index 5710cfe6..88a9a372 100644 --- a/src/utils/arweave.test.ts +++ b/tests/utils/arweave.test.ts @@ -1,5 +1,5 @@ -import { ARNS_DEVNET_REGISTRY_TX } from '../constants.js'; -import { validateArweaveId } from './arweave.js'; +import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; +import { validateArweaveId } from '../../src/utils/arweave.js'; describe('Arweave ID Validation', () => { it('should validate a valid Arweave ID', () => { diff --git a/tsconfig.json b/tsconfig.json index 5f894ed0..c8ca7a46 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,6 @@ "skipLibCheck": true, "strictNullChecks": true }, - "include": ["src"], + "include": ["src", "tests/utils/arweave.test.ts"], "exclude": ["lib", "node_modules", "bundles"] } diff --git a/yarn.lock b/yarn.lock index fb2d95c9..3826436e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2162,17 +2162,7 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== -arweave@1.13.7: - version "1.13.7" - resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.13.7.tgz#cda8534c833baec372a7052c61f53b4e39a886d7" - integrity sha512-Hv+x2bSI6UyBHpuVbUDMMpMje1ETfpJWj52kKfz44O0IqDRi/LukOkkDUptup1p6OT6KP1/DdpnUnsNHoskFeA== - dependencies: - arconnect "^0.4.2" - asn1.js "^5.4.1" - base64-js "^1.5.1" - bignumber.js "^9.0.2" - -arweave@^1.10.13, arweave@^1.13.7, arweave@^1.14.4: +arweave@1.14.4, arweave@^1.10.13, arweave@^1.13.7, arweave@^1.14.4: version "1.14.4" resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.14.4.tgz#5ba22136aa0e7fd9495258a3931fb770c9d6bf21" integrity sha512-tmqU9fug8XAmFETYwgUhLaD3WKav5DaM4p1vgJpEj/Px2ORPPMikwnSySlFymmL2qgRh2ZBcZsg11+RXPPGLsA== @@ -8074,13 +8064,13 @@ warp-arbundles@^1.0.4: buffer "^6.0.3" warp-isomorphic "^1.0.7" -warp-contracts@1.4.29: - version "1.4.29" - resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.29.tgz#fbab7b64ad055f7245229d9bfab992161fda2f33" - integrity sha512-cwNgKmKl5vMd5ptLeyPqMug5IPCyQIJZMalQ7FBFZjLMC7atlMcNYDKgaBHi14vXjtTtw1g+V6BRETwQhqCfzA== +warp-contracts@^1.4.37: + version "1.4.37" + resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.37.tgz#d22d34750bb50638188ca40d53015207688d1f58" + integrity sha512-jDQQF4FqRCy6OAnsb87d9LMzVp18eRTFyDQp9QirLD2C7IwBAH2DVrfpVcyPeZuxw4bNz3CSVemdMS+oFJIldQ== dependencies: archiver "^5.3.0" - arweave "1.13.7" + arweave "1.14.4" async-mutex "^0.4.0" bignumber.js "9.1.1" events "3.3.0" From a3d9801a81f0d5d393bbcc506c0e7b656bc0375a Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 8 Mar 2024 08:41:26 -0600 Subject: [PATCH 15/18] chore: update github action to run against local servcie, formatting --- .github/workflows/build.yml | 2 +- README.md | 4 ++-- docker-compose.yaml | 6 +++--- tests/ar-io.test.ts | 33 ++++++++++++++++++--------------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa7d3cc9..74fa8b39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: node_version: [18.x, 20.x] - command: ['lint', 'format', 'test', 'build'] + command: ['lint', 'format', 'test:integration', 'build'] steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 3a943a51..c7e63e3d 100644 --- a/README.md +++ b/README.md @@ -149,14 +149,14 @@ const remoteCustomArIO = new ArIO({ const localCustomArIO = new ArIO({ contract: new WarpContract({ contractTxId: 'TESTNET_CONTRACT_TX_ID', - }) + }), }); // provide a custom contract to the client, and specify local evaluation using remote cache const remoteCacheCustomArIO = new ArIO({ contract: new RemoteContract({ contractTxId: 'TESTNET_CONTRACT_TX_ID', - }) + }), }); ``` diff --git a/docker-compose.yaml b/docker-compose.yaml index 8b0dc80c..f856b1db 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,18 +1,18 @@ version: '3' services: - arns-service: + arns-service: image: ghcr.io/ar-io/arns-service:latest build: . ports: - - "3000:3000" + - '3000:3000' environment: - LOG_LEVEL=debug - PREFETCH_CONTRACTS=true - PREFETCH_CONTRACT_IDS=_NctcA2sRy1-J4OmIQZbYFPM17piNcbdBPH2ncX2RL8 - BOOTSTRAP_CONTRACTS=false healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/healthcheck"] + test: ['CMD', 'curl', '-f', 'http://localhost:3000/healthcheck'] interval: 10s timeout: 5s retries: 5 diff --git a/tests/ar-io.test.ts b/tests/ar-io.test.ts index 26bfce4d..79238dbb 100644 --- a/tests/ar-io.test.ts +++ b/tests/ar-io.test.ts @@ -14,7 +14,7 @@ describe('ArIO Client', () => { const arioClient = new ArIO({ contract: new RemoteContract({ url: process.env.REMOTE_CACHE_URL || 'http://localhost:3000', - contractTxId: ARNS_DEVNET_REGISTRY_TX + contractTxId: ARNS_DEVNET_REGISTRY_TX, }), }); it('should create a custom ArIO client', () => { @@ -22,8 +22,10 @@ describe('ArIO Client', () => { }); it('should should return undefined for non existent gateway', async () => { - const nonExistent = await arioClient.getGateway({ address: 'some-address' }) - expect(nonExistent).toEqual(undefined) + const nonExistent = await arioClient.getGateway({ + address: 'some-address', + }); + expect(nonExistent).toEqual(undefined); }); it('should return gateway state at a given block height', async () => { @@ -56,14 +58,16 @@ describe('ArIO Client', () => { expect(gateways[gatewayAddress]).toBeDefined(); }); - it('should fetch a record', async () => { + it('should fetch a record', async () => { const record = await arioClient.getArNSRecord({ domain: 'ar-io' }); expect(record).toBeDefined(); }); it('should throw NotFound error on non existent record', async () => { - const nonExistent = await arioClient.getArNSRecord({ domain: 'some-domain' }) - expect(nonExistent).toEqual(undefined) + const nonExistent = await arioClient.getArNSRecord({ + domain: 'some-domain', + }); + expect(nonExistent).toEqual(undefined); }); it('should fetch all records', async () => { @@ -80,15 +84,14 @@ describe('ArIO Client', () => { }); expect(currentRecord).toBeDefined(); - const nonExistent = await arioClient - .getArNSRecord({ - domain, - evaluationOptions: { - evalTo: { blockHeight: 0 }, - }, - }) - - expect(nonExistent).toEqual(undefined) + const nonExistent = await arioClient.getArNSRecord({ + domain, + evaluationOptions: { + evalTo: { blockHeight: 0 }, + }, + }); + + expect(nonExistent).toEqual(undefined); }); it('should return record at a given sort key', async () => { From 1c0897e0ba266dfd153582b41c5a24e7d941fd7a Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 8 Mar 2024 08:45:10 -0600 Subject: [PATCH 16/18] chore: pin warp to 1.4.29 to avoid build issues --- package.json | 2 +- yarn.lock | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 467e197f..d6a0af53 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "dependencies": { "arweave": "^1.14.4", "axios": "1.4.0", - "warp-contracts": "^1.4.37", + "warp-contracts": "1.4.29", "winston": "^3.11.0" } } diff --git a/yarn.lock b/yarn.lock index 3826436e..fb2d95c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2162,7 +2162,17 @@ arrify@^1.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== -arweave@1.14.4, arweave@^1.10.13, arweave@^1.13.7, arweave@^1.14.4: +arweave@1.13.7: + version "1.13.7" + resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.13.7.tgz#cda8534c833baec372a7052c61f53b4e39a886d7" + integrity sha512-Hv+x2bSI6UyBHpuVbUDMMpMje1ETfpJWj52kKfz44O0IqDRi/LukOkkDUptup1p6OT6KP1/DdpnUnsNHoskFeA== + dependencies: + arconnect "^0.4.2" + asn1.js "^5.4.1" + base64-js "^1.5.1" + bignumber.js "^9.0.2" + +arweave@^1.10.13, arweave@^1.13.7, arweave@^1.14.4: version "1.14.4" resolved "https://registry.yarnpkg.com/arweave/-/arweave-1.14.4.tgz#5ba22136aa0e7fd9495258a3931fb770c9d6bf21" integrity sha512-tmqU9fug8XAmFETYwgUhLaD3WKav5DaM4p1vgJpEj/Px2ORPPMikwnSySlFymmL2qgRh2ZBcZsg11+RXPPGLsA== @@ -8064,13 +8074,13 @@ warp-arbundles@^1.0.4: buffer "^6.0.3" warp-isomorphic "^1.0.7" -warp-contracts@^1.4.37: - version "1.4.37" - resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.37.tgz#d22d34750bb50638188ca40d53015207688d1f58" - integrity sha512-jDQQF4FqRCy6OAnsb87d9LMzVp18eRTFyDQp9QirLD2C7IwBAH2DVrfpVcyPeZuxw4bNz3CSVemdMS+oFJIldQ== +warp-contracts@1.4.29: + version "1.4.29" + resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.29.tgz#fbab7b64ad055f7245229d9bfab992161fda2f33" + integrity sha512-cwNgKmKl5vMd5ptLeyPqMug5IPCyQIJZMalQ7FBFZjLMC7atlMcNYDKgaBHi14vXjtTtw1g+V6BRETwQhqCfzA== dependencies: archiver "^5.3.0" - arweave "1.14.4" + arweave "1.13.7" async-mutex "^0.4.0" bignumber.js "9.1.1" events "3.3.0" From d484544a843dc77b357815f8fa84e3994256933f Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 8 Mar 2024 09:02:06 -0600 Subject: [PATCH 17/18] chore: move type files up and cleanup exports --- src/{types => }/arns-service.ts | 0 src/{types => }/common.ts | 0 src/common/ar-io.ts | 4 ++-- src/common/contracts/index.ts | 18 ------------------ src/common/contracts/remote-contract.ts | 2 +- src/common/contracts/warp-contract.ts | 16 +++++++--------- src/common/http.ts | 2 +- src/common/index.ts | 5 ++++- src/common/logger.ts | 2 +- src/{types => }/contract-state.ts | 0 src/node/index.ts | 2 +- src/{types/index.ts => types.ts} | 1 - src/utils/arweave.ts | 2 +- src/utils/smartweave.ts | 2 +- src/web/index.ts | 2 +- tests/ar-io.test.ts | 4 ++-- tests/utils/arweave.test.ts | 14 -------------- tsconfig.json | 4 ++-- 18 files changed, 24 insertions(+), 56 deletions(-) rename src/{types => }/arns-service.ts (100%) rename src/{types => }/common.ts (100%) delete mode 100644 src/common/contracts/index.ts rename src/{types => }/contract-state.ts (100%) rename src/{types/index.ts => types.ts} (99%) delete mode 100644 tests/utils/arweave.test.ts diff --git a/src/types/arns-service.ts b/src/arns-service.ts similarity index 100% rename from src/types/arns-service.ts rename to src/arns-service.ts diff --git a/src/types/common.ts b/src/common.ts similarity index 100% rename from src/types/common.ts rename to src/common.ts diff --git a/src/common/ar-io.ts b/src/common/ar-io.ts index 8356dc67..b5679b16 100644 --- a/src/common/ar-io.ts +++ b/src/common/ar-io.ts @@ -22,8 +22,8 @@ import { EvaluationParameters, Gateway, SmartWeaveContract, -} from '../types/index.js'; -import { RemoteContract } from './index.js'; +} from '../types.js'; +import { RemoteContract } from './contracts/remote-contract.js'; // TODO: append this with other configuration options (e.g. local vs. remote evaluation) export type ContractConfiguration = diff --git a/src/common/contracts/index.ts b/src/common/contracts/index.ts deleted file mode 100644 index 624451ad..00000000 --- a/src/common/contracts/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (C) 2022-2024 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 . - */ -export * from './remote-contract.js'; -export * from './warp-contract.js'; diff --git a/src/common/contracts/remote-contract.ts b/src/common/contracts/remote-contract.ts index c2c27549..8679f1c7 100644 --- a/src/common/contracts/remote-contract.ts +++ b/src/common/contracts/remote-contract.ts @@ -19,7 +19,7 @@ import { HTTPClient, Logger, SmartWeaveContract, -} from '../../types/index.js'; +} from '../../types.js'; import { AxiosHTTPService } from '../http.js'; import { DefaultLogger } from '../logger.js'; diff --git a/src/common/contracts/warp-contract.ts b/src/common/contracts/warp-contract.ts index 2fbce91c..bc664f5b 100644 --- a/src/common/contracts/warp-contract.ts +++ b/src/common/contracts/warp-contract.ts @@ -21,14 +21,9 @@ import { defaultCacheOptions, } from 'warp-contracts'; -import { EvaluationParameters, SmartWeaveContract } from '../../types/index.js'; +import { EvaluationParameters, SmartWeaveContract } from '../../types.js'; import { FailedRequestError } from '../error.js'; -export const defaultWarpClient = WarpFactory.forMainnet({ - ...defaultCacheOptions, - inMemory: true, // default to in memory for now, a custom warp implementation can be provided -}); - export class WarpContract implements SmartWeaveContract { private contract: Contract; private contractTxId: string; @@ -37,18 +32,21 @@ export class WarpContract implements SmartWeaveContract { constructor({ contractTxId, cacheUrl, - warp = defaultWarpClient, + warp = WarpFactory.forMainnet({ + ...defaultCacheOptions, + inMemory: true, // default to in memory for now, a custom warp implementation can be provided + }), }: { + contractTxId: string; cacheUrl?: string; warp: Warp; - contractTxId: string; }) { - // sync state this.contract = warp.contract(contractTxId); this.cacheUrl = cacheUrl; } private async syncState() { + // TODO: get contract manifest and set it before evaluating if (this.cacheUrl !== undefined) { await this.contract.syncState( `${this.cacheUrl}/v1/contract/${this.contractTxId}`, diff --git a/src/common/http.ts b/src/common/http.ts index 7e566f0c..1c5f5cb3 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -16,7 +16,7 @@ */ import { AxiosInstance } from 'axios'; -import { HTTPClient, Logger } from '../types/index.js'; +import { HTTPClient, Logger } from '../types.js'; import { createAxiosInstance } from '../utils/index.js'; import { FailedRequestError, NotFound, UnknownError } from './error.js'; import { DefaultLogger } from './logger.js'; diff --git a/src/common/index.ts b/src/common/index.ts index be44392a..e21bcabd 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -15,6 +15,9 @@ * along with this program. If not, see . */ export * from './ar-io.js'; -export * from './contracts/index.js'; export * from './error.js'; export * from './logger.js'; + +// contracts +export * from './contracts/remote-contract.js'; +export * from './contracts/warp-contract.js'; diff --git a/src/common/logger.ts b/src/common/logger.ts index 623c25b0..25aeb7f8 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -18,7 +18,7 @@ import 'setimmediate'; import winston, { createLogger, format, transports } from 'winston'; -import { Logger } from '../types/index.js'; +import { Logger } from '../types.js'; import { version } from '../version.js'; export class DefaultLogger implements Logger { diff --git a/src/types/contract-state.ts b/src/contract-state.ts similarity index 100% rename from src/types/contract-state.ts rename to src/contract-state.ts diff --git a/src/node/index.ts b/src/node/index.ts index d71e24b7..f72e7bc6 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -export * from '../types/index.js'; +export * from '../types.js'; export * from '../common/index.js'; export * from '../constants.js'; export * from '../utils/index.js'; diff --git a/src/types/index.ts b/src/types.ts similarity index 99% rename from src/types/index.ts rename to src/types.ts index 476e1888..3b6b66f9 100644 --- a/src/types/index.ts +++ b/src/types.ts @@ -14,7 +14,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - export * from './arns-service.js'; export * from './contract-state.js'; export * from './common.js'; diff --git a/src/utils/arweave.ts b/src/utils/arweave.ts index f5df514f..8b65fdef 100644 --- a/src/utils/arweave.ts +++ b/src/utils/arweave.ts @@ -14,8 +14,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { BlockHeight } from '../common.js'; import { ARWEAVE_TX_REGEX } from '../constants.js'; -import { BlockHeight } from '../types/common.js'; export const validateArweaveId = (id: string): boolean => { return ARWEAVE_TX_REGEX.test(id); diff --git a/src/utils/smartweave.ts b/src/utils/smartweave.ts index c13fb4ac..762f4aee 100644 --- a/src/utils/smartweave.ts +++ b/src/utils/smartweave.ts @@ -14,8 +14,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import { SortKey } from '../common.js'; import { SORT_KEY_REGEX } from '../constants.js'; -import { SortKey } from '../types/common.js'; export function isSortKey(sortKey: string): sortKey is SortKey { return SmartWeaveSortKey.validate(sortKey); diff --git a/src/web/index.ts b/src/web/index.ts index d71e24b7..f72e7bc6 100644 --- a/src/web/index.ts +++ b/src/web/index.ts @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -export * from '../types/index.js'; +export * from '../types.js'; export * from '../common/index.js'; export * from '../constants.js'; export * from '../utils/index.js'; diff --git a/tests/ar-io.test.ts b/tests/ar-io.test.ts index 79238dbb..33457d4b 100644 --- a/tests/ar-io.test.ts +++ b/tests/ar-io.test.ts @@ -1,7 +1,7 @@ import { ArIO } from '../src/common/ar-io.js'; -import { RemoteContract } from '../src/common/index.js'; +import { RemoteContract } from '../src/common/contracts/remote-contract.js'; import { ARNS_DEVNET_REGISTRY_TX } from '../src/constants.js'; -import { ArIOState } from '../src/types/contract-state.js'; +import { ArIOState } from '../src/contract-state.js'; import { SmartWeaveSortKey } from '../src/utils/smartweave.js'; const gatewayAddress = '1H7WZIWhzwTH9FIcnuMqYkTsoyv1OTfGa_amvuYwrgo'; diff --git a/tests/utils/arweave.test.ts b/tests/utils/arweave.test.ts deleted file mode 100644 index 88a9a372..00000000 --- a/tests/utils/arweave.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js'; -import { validateArweaveId } from '../../src/utils/arweave.js'; - -describe('Arweave ID Validation', () => { - it('should validate a valid Arweave ID', () => { - const validId = ARNS_DEVNET_REGISTRY_TX; - expect(validateArweaveId(validId)).toBe(true); - }); - - it('should not validate an invalid Arweave ID', () => { - const invalidId = 'invalid-id'; - expect(validateArweaveId(invalidId)).toBe(false); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index c8ca7a46..4dd6d5f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,6 @@ "skipLibCheck": true, "strictNullChecks": true }, - "include": ["src", "tests/utils/arweave.test.ts"], - "exclude": ["lib", "node_modules", "bundles"] + "include": ["src"], + "exclude": ["lib", "node_modules", "bundles", "tests"] } From 86b00a60393c139508c6547600dff1c378e7a3a9 Mon Sep 17 00:00:00 2001 From: dtfiedler Date: Fri, 8 Mar 2024 09:06:11 -0600 Subject: [PATCH 18/18] chore: update tests --- tests/ar-io.test.ts | 49 ++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/tests/ar-io.test.ts b/tests/ar-io.test.ts index 33457d4b..c12b4336 100644 --- a/tests/ar-io.test.ts +++ b/tests/ar-io.test.ts @@ -11,91 +11,82 @@ const evaluateToSortKey = new SmartWeaveSortKey( '000001376946,0000000000000,18d52956c8e13ae1f557b4e67f6f298b8ffd2a5cd96e42ec24ca649b7401510f', ); describe('ArIO Client', () => { - const arioClient = new ArIO({ + const arIO = new ArIO({ contract: new RemoteContract({ url: process.env.REMOTE_CACHE_URL || 'http://localhost:3000', contractTxId: ARNS_DEVNET_REGISTRY_TX, }), }); it('should create a custom ArIO client', () => { - expect(arioClient).toBeInstanceOf(ArIO); + expect(arIO).toBeInstanceOf(ArIO); }); it('should should return undefined for non existent gateway', async () => { - const nonExistent = await arioClient.getGateway({ + const nonExistent = await arIO.getGateway({ address: 'some-address', }); expect(nonExistent).toEqual(undefined); }); - it('should return gateway state at a given block height', async () => { - const gateway = await arioClient.getGateway({ + it('should return gateways at a given block height', async () => { + const gateway = await arIO.getGateway({ address: gatewayAddress, evaluationOptions: { evalTo: { blockHeight: evaluateToBlockHeight } }, }); expect(gateway).toBeDefined(); }); - it('should return gateway state at a given sort key', async () => { - const gateway = await arioClient.getGateway({ + it('should return gateways at a given sort key', async () => { + const gateway = await arIO.getGateway({ address: gatewayAddress, evaluationOptions: { evalTo: { sortKey: evaluateToSortKey.toString() } }, }); expect(gateway).toBeDefined(); }); - it('should return gateways state at a given block height', async () => { - const gateways = await arioClient.getGateways({ + it('should return gateways at a given block height', async () => { + const gateways = await arIO.getGateways({ evaluationOptions: { evalTo: { blockHeight: evaluateToBlockHeight } }, }); expect(gateways[gatewayAddress]).toBeDefined(); }); - it('should return gateways state at a given sort key', async () => { - const gateways = await arioClient.getGateways({ + it('should return gateways at a given sort key', async () => { + const gateways = await arIO.getGateways({ evaluationOptions: { evalTo: { sortKey: evaluateToSortKey.toString() } }, }); expect(gateways[gatewayAddress]).toBeDefined(); }); - it('should fetch a record', async () => { - const record = await arioClient.getArNSRecord({ domain: 'ar-io' }); + it('should return the record for an existing domain', async () => { + const record = await arIO.getArNSRecord({ domain }); expect(record).toBeDefined(); }); - it('should throw NotFound error on non existent record', async () => { - const nonExistent = await arioClient.getArNSRecord({ + it('should throw return undefined for a non existent record', async () => { + const nonExistent = await arIO.getArNSRecord({ domain: 'some-domain', }); expect(nonExistent).toEqual(undefined); }); it('should fetch all records', async () => { - const records = await arioClient.getArNSRecords(); + const records = await arIO.getArNSRecords(); expect(records).toBeDefined(); }); it('should return record at a given block height', async () => { - const currentRecord = await arioClient.getArNSRecord({ + const currentRecord = await arIO.getArNSRecord({ domain, evaluationOptions: { evalTo: { blockHeight: evaluateToBlockHeight + 1 }, }, }); expect(currentRecord).toBeDefined(); - - const nonExistent = await arioClient.getArNSRecord({ - domain, - evaluationOptions: { - evalTo: { blockHeight: 0 }, - }, - }); - - expect(nonExistent).toEqual(undefined); }); it('should return record at a given sort key', async () => { - const record = await arioClient.getArNSRecord({ + const record = await arIO.getArNSRecord({ domain, evaluationOptions: { evalTo: { sortKey: evaluateToSortKey.toString() }, @@ -105,7 +96,7 @@ describe('ArIO Client', () => { }); it('should return records at a given block height', async () => { - const records = await arioClient.getArNSRecords({ + const records = await arIO.getArNSRecords({ evaluationOptions: { evalTo: { blockHeight: evaluateToBlockHeight }, }, @@ -114,7 +105,7 @@ describe('ArIO Client', () => { }); it('should return records at a given sort key', async () => { - const records = await arioClient.getArNSRecords({ + const records = await arIO.getArNSRecords({ evaluationOptions: { evalTo: { sortKey: evaluateToSortKey.toString() }, },