diff --git a/packages/cli/src/__tests__/ceramic-error.test.ts b/packages/cli/src/__tests__/ceramic-error.test.ts index d3e471abb5..0b865a39cd 100644 --- a/packages/cli/src/__tests__/ceramic-error.test.ts +++ b/packages/cli/src/__tests__/ceramic-error.test.ts @@ -2,7 +2,7 @@ import getPort from 'get-port' import { IpfsApi, LogLevel } from '@ceramicnetwork/common' import * as tmp from 'tmp-promise' import { createIPFS } from '@ceramicnetwork/ipfs-daemon' -import { Ceramic } from '@ceramicnetwork/core' +import { Ceramic, VersionInfo } from '@ceramicnetwork/core' import * as random from '@stablelib/random' import { CeramicDaemon, makeCeramicConfig } from '../ceramic-daemon.js' import { CeramicClient } from '@ceramicnetwork/http-client' @@ -58,7 +58,7 @@ beforeAll(async () => { const ceramicConfig = makeCeramicConfig(daemonConfig) ceramicConfig.enableAnchorPollingLoop = false - core = await Ceramic.create(ipfs, ceramicConfig) + core = await Ceramic.create(ipfs, ceramicConfig, {} as VersionInfo) daemon = new CeramicDaemon(core, daemonConfig) await daemon.listen() const apiUrl = `http://localhost:${daemonPort}` diff --git a/packages/cli/src/__tests__/ceramic-multi-daemon.test.ts b/packages/cli/src/__tests__/ceramic-multi-daemon.test.ts index ca1a9ffce9..d873fb2485 100644 --- a/packages/cli/src/__tests__/ceramic-multi-daemon.test.ts +++ b/packages/cli/src/__tests__/ceramic-multi-daemon.test.ts @@ -1,5 +1,5 @@ import { jest } from '@jest/globals' -import { Ceramic } from '@ceramicnetwork/core' +import { Ceramic, VersionInfo } from '@ceramicnetwork/core' import { CeramicClient } from '@ceramicnetwork/http-client' import * as tmp from 'tmp-promise' import { CeramicDaemon } from '../ceramic-daemon.js' @@ -16,16 +16,20 @@ const seed = 'SEED' const TOPIC = '/ceramic' const makeCeramicCore = async (ipfs: IpfsApi, stateStoreDirectory: string): Promise => { - const core = await Ceramic.create(ipfs, { - pubsubTopic: TOPIC, - stateStoreDirectory, - anchorOnRequest: false, - indexing: { - db: `sqlite://${stateStoreDirectory}/ceramic.sqlite`, - disableComposedb: false, + const core = await Ceramic.create( + ipfs, + { + pubsubTopic: TOPIC, + stateStoreDirectory, + anchorOnRequest: false, + indexing: { + db: `sqlite://${stateStoreDirectory}/ceramic.sqlite`, + disableComposedb: false, + }, + anchorLoopMinDurationMs: 0, }, - anchorLoopMinDurationMs: 0, - }) + {} as VersionInfo + ) const handler = new TileDocumentHandler() handler.verifyJWS = (): Promise => { diff --git a/packages/cli/src/__tests__/make-ceramic-core.ts b/packages/cli/src/__tests__/make-ceramic-core.ts index adc3065870..a525d097a9 100644 --- a/packages/cli/src/__tests__/make-ceramic-core.ts +++ b/packages/cli/src/__tests__/make-ceramic-core.ts @@ -1,23 +1,27 @@ import { IpfsApi } from '@ceramicnetwork/common' -import { Ceramic } from '@ceramicnetwork/core' +import { Ceramic, VersionInfo } from '@ceramicnetwork/core' import { TileDocumentHandler } from '@ceramicnetwork/stream-tile-handler' export async function makeCeramicCore( ipfs: IpfsApi, stateStoreDirectory: string ): Promise { - const core = await Ceramic.create(ipfs, { - pubsubTopic: '/ceramic', - stateStoreDirectory, - anchorOnRequest: false, - indexing: { - db: `sqlite://${stateStoreDirectory}/ceramic.sqlite`, - allowQueriesBeforeHistoricalSync: true, - disableComposedb: false, - enableHistoricalSync: false, + const core = await Ceramic.create( + ipfs, + { + pubsubTopic: '/ceramic', + stateStoreDirectory, + anchorOnRequest: false, + indexing: { + db: `sqlite://${stateStoreDirectory}/ceramic.sqlite`, + allowQueriesBeforeHistoricalSync: true, + disableComposedb: false, + enableHistoricalSync: false, + }, + anchorLoopMinDurationMs: 0, }, - anchorLoopMinDurationMs: 0, - }) + {} as VersionInfo + ) const handler = new TileDocumentHandler() ;(handler as any).verifyJWS = (): Promise => { diff --git a/packages/cli/src/ceramic-daemon.ts b/packages/cli/src/ceramic-daemon.ts index 5ebf1747f9..4716652e77 100644 --- a/packages/cli/src/ceramic-daemon.ts +++ b/packages/cli/src/ceramic-daemon.ts @@ -282,9 +282,14 @@ export class CeramicDaemon { ceramicConfig.loggerProvider.getDiagnosticsLogger(), opts.ipfs?.host ) + const ipfsId = await ipfs.id() - const [modules, params] = Ceramic._processConfig(ipfs, ceramicConfig) - params.versionInfo = { cliPackageVersion: version, gitHash: commitHash } + const versionInfo = { + cliPackageVersion: version, + gitHash: commitHash, + ceramicOneVersion: ipfsId.agentVersion, + } + const [modules, params] = Ceramic._processConfig(ipfs, ceramicConfig, versionInfo) const diagnosticsLogger = modules.loggerProvider.getDiagnosticsLogger() diagnosticsLogger.imp( `Starting Ceramic Daemon with @ceramicnetwork/cli package version ${version}, with js-ceramic repo git hash ${commitHash}, and with config: \n${JSON.stringify( @@ -293,7 +298,6 @@ export class CeramicDaemon { 2 )}` ) - const ipfsId = await ipfs.id() diagnosticsLogger.imp( `Connecting to IPFS node with version "${ipfsId.agentVersion}" available as ${ipfsId.addresses .map(String) diff --git a/packages/core/src/__tests__/ceramic-pinning.test.ts b/packages/core/src/__tests__/ceramic-pinning.test.ts index 98e1a2de6b..f0612800e5 100644 --- a/packages/core/src/__tests__/ceramic-pinning.test.ts +++ b/packages/core/src/__tests__/ceramic-pinning.test.ts @@ -1,5 +1,5 @@ import { jest, describe, expect, beforeEach, afterEach } from '@jest/globals' -import { Ceramic } from '../ceramic.js' +import { Ceramic, VersionInfo } from '../ceramic.js' import { Ed25519Provider } from 'key-did-provider-ed25519' import tmp from 'tmp-promise' import { IpfsApi, SyncOptions } from '@ceramicnetwork/common' @@ -47,18 +47,22 @@ const createCeramic = async ( const databaseFolder = await tmp.dir({ unsafeCleanup: true }) const connectionString = new URL(`sqlite://${databaseFolder.path}/ceramic.sqlite`) - const ceramic = await Ceramic.create(ipfs, { - stateStoreDirectory, - anchorOnRequest, - indexing: { - db: connectionString.href, - allowQueriesBeforeHistoricalSync: true, - disableComposedb: true, - enableHistoricalSync: false, + const ceramic = await Ceramic.create( + ipfs, + { + stateStoreDirectory, + anchorOnRequest, + indexing: { + db: connectionString.href, + allowQueriesBeforeHistoricalSync: true, + disableComposedb: true, + enableHistoricalSync: false, + }, + pubsubTopic: '/ceramic/inmemory/test', // necessary so Ceramic instances can talk to each other + anchorLoopMinDurationMs: 0, }, - pubsubTopic: '/ceramic/inmemory/test', // necessary so Ceramic instances can talk to each other - anchorLoopMinDurationMs: 0, - }) + {} as VersionInfo + ) ceramic.did = makeDID(seed, ceramic) await ceramic.did.authenticate() diff --git a/packages/core/src/__tests__/create-ceramic.ts b/packages/core/src/__tests__/create-ceramic.ts index 6bbef92507..10728c54d9 100644 --- a/packages/core/src/__tests__/create-ceramic.ts +++ b/packages/core/src/__tests__/create-ceramic.ts @@ -9,6 +9,8 @@ import * as KeyDidResolver from 'key-did-resolver' import { Resolver } from 'did-resolver' import { DID } from 'dids' +const VERSION_INFO = { cliPackageVersion: '', gitHash: '', ceramicOneVersion: '' } + export async function createCeramic( ipfs: IpfsApi, config?: CeramicConfig & { @@ -33,7 +35,7 @@ export async function createCeramic( anchorLoopMinDurationMs: 0, ...config, } - const ceramic = await Ceramic.create(ipfs, appliedConfig) + const ceramic = await Ceramic.create(ipfs, appliedConfig, VERSION_INFO) const seed = sha256.hash(uint8arrays.fromString(appliedConfig.seed || 'SEED')) const provider = new Ed25519Provider(seed) const keyDidResolver = KeyDidResolver.getResolver() diff --git a/packages/core/src/__tests__/initialization.test.ts b/packages/core/src/__tests__/initialization.test.ts index 4facca5d94..84d5689074 100644 --- a/packages/core/src/__tests__/initialization.test.ts +++ b/packages/core/src/__tests__/initialization.test.ts @@ -6,6 +6,8 @@ import { Networks } from '@ceramicnetwork/common' import { createIPFS } from '@ceramicnetwork/ipfs-daemon' import { InMemoryAnchorService } from '../anchor/memory/in-memory-anchor-service.js' +const VERSION_INFO = { cliPackageVersion: '', gitHash: '', ceramicOneVersion: '' } + describe('Ceramic integration', () => { jest.setTimeout(60000) let ipfs1: IpfsApi @@ -21,11 +23,15 @@ describe('Ceramic integration', () => { it('can create Ceramic instance on default network', async () => { const stateStoreDirectory = await tmp.tmpName() const databaseConnectionString = new URL(`sqlite://${stateStoreDirectory}/ceramic.sqlite`) - const ceramic = await Ceramic.create(ipfs1, { - stateStoreDirectory, - indexing: { db: databaseConnectionString.href, models: [] }, - anchorLoopMinDurationMs: 0, - }) + const ceramic = await Ceramic.create( + ipfs1, + { + stateStoreDirectory, + indexing: { db: databaseConnectionString.href }, + anchorLoopMinDurationMs: 0, + }, + VERSION_INFO + ) const supportedChains = await ceramic.getSupportedChains() expect(supportedChains).toEqual(['inmemory:12345']) await ceramic.close() @@ -34,12 +40,16 @@ describe('Ceramic integration', () => { it('can create Ceramic instance explicitly on inmemory network', async () => { const stateStoreDirectory = await tmp.tmpName() const databaseConnectionString = new URL(`sqlite://${stateStoreDirectory}/ceramic.sqlite`) - const ceramic = await Ceramic.create(ipfs1, { - networkName: 'inmemory', - stateStoreDirectory, - indexing: { db: databaseConnectionString.href, models: [] }, - anchorLoopMinDurationMs: 0, - }) + const ceramic = await Ceramic.create( + ipfs1, + { + networkName: 'inmemory', + stateStoreDirectory, + indexing: { db: databaseConnectionString.href }, + anchorLoopMinDurationMs: 0, + }, + VERSION_INFO + ) const supportedChains = await ceramic.getSupportedChains() expect(supportedChains).toEqual(['inmemory:12345']) await ceramic.close() @@ -48,11 +58,15 @@ describe('Ceramic integration', () => { it('cannot create Ceramic instance on network not supported by our anchor service', async () => { const tmpDirectory = await tmp.tmpName() const databaseConnectionString = new URL(`sqlite://${tmpDirectory}/ceramic.sqlite`) - const [modules, params] = Ceramic._processConfig(ipfs1, { - networkName: 'local', - indexing: { db: databaseConnectionString.href, models: [] }, - anchorLoopMinDurationMs: 0, - }) + const [modules, params] = Ceramic._processConfig( + ipfs1, + { + networkName: 'local', + indexing: { db: databaseConnectionString.href }, + anchorLoopMinDurationMs: 0, + }, + VERSION_INFO + ) modules.anchorService = new InMemoryAnchorService( {}, modules.loggerProvider.getDiagnosticsLogger() @@ -67,12 +81,16 @@ describe('Ceramic integration', () => { const stateStoreDirectory = await tmp.tmpName() const databaseConnectionString = new URL(`sqlite://${stateStoreDirectory}/ceramic.sqlite`) await expect( - Ceramic.create(ipfs1, { - networkName: 'fakenetwork', - stateStoreDirectory, - indexing: { db: databaseConnectionString.href, models: [] }, - anchorLoopMinDurationMs: 0, - }) + Ceramic.create( + ipfs1, + { + networkName: 'fakenetwork', + stateStoreDirectory, + indexing: { db: databaseConnectionString.href }, + anchorLoopMinDurationMs: 0, + }, + VERSION_INFO + ) ).rejects.toThrow( "Unrecognized Ceramic network name: 'fakenetwork'. Supported networks are: 'mainnet', 'testnet-clay', 'dev-unstable', 'local', 'inmemory'" ) @@ -81,12 +99,16 @@ describe('Ceramic integration', () => { test('init dispatcher', async () => { const tmpDirectory = await tmp.tmpName() const databaseConnectionString = new URL(`sqlite://${tmpDirectory}/ceramic.sqlite`) - const [modules, params] = await Ceramic._processConfig(ipfs1, { - networkName: Networks.INMEMORY, - stateStoreDirectory: tmpDirectory, - indexing: { db: databaseConnectionString.href, models: [] }, - anchorLoopMinDurationMs: 0, - }) + const [modules, params] = await Ceramic._processConfig( + ipfs1, + { + networkName: Networks.INMEMORY, + stateStoreDirectory: tmpDirectory, + indexing: { db: databaseConnectionString.href }, + anchorLoopMinDurationMs: 0, + }, + VERSION_INFO + ) const dispatcher = modules.dispatcher const ceramic = new Ceramic(modules, params) const dispatcherInitSpy = jest.spyOn(dispatcher, 'init') diff --git a/packages/core/src/anchor/ethereum/__tests__/authenticated-anchor-service.test.ts b/packages/core/src/anchor/ethereum/__tests__/authenticated-anchor-service.test.ts index 584e956122..f3ccce0d1c 100644 --- a/packages/core/src/anchor/ethereum/__tests__/authenticated-anchor-service.test.ts +++ b/packages/core/src/anchor/ethereum/__tests__/authenticated-anchor-service.test.ts @@ -11,8 +11,7 @@ import { createDidAnchorServiceAuth } from '../../../__tests__/create-did-anchor import { AuthenticatedEthereumAnchorService } from '../ethereum-anchor-service.js' import { AnchorRequestStore } from '../../../store/anchor-request-store.js' import type { AnchorLoopHandler } from '../../anchor-service.js' -import { CARFactory, type CAR } from 'cartonne' -import { Ceramic } from '../../../ceramic.js' +import { Ceramic, VersionInfo } from '../../../ceramic.js' import { BaseTestUtils } from '@ceramicnetwork/base-test-utils' const FAUX_ANCHOR_STORE = { @@ -28,6 +27,7 @@ const FAUX_HANDLER: AnchorLoopHandler = { } const diagnosticsLogger = new LoggerProvider().getDiagnosticsLogger() +const VERSION_INFO: VersionInfo = { cliPackageVersion: '', gitHash: '', ceramicOneVersion: '' } describe('AuthenticatedEthereumAnchorServiceTest', () => { let ipfs: IpfsApi @@ -53,7 +53,13 @@ describe('AuthenticatedEthereumAnchorServiceTest', () => { const auth = createDidAnchorServiceAuth(url, ceramic.signer, diagnosticsLogger, fauxFetchJson) const signRequestSpy = jest.spyOn(auth, 'signRequest') - const anchorService = new AuthenticatedEthereumAnchorService(auth, url, url, diagnosticsLogger) + const anchorService = new AuthenticatedEthereumAnchorService( + auth, + url, + url, + diagnosticsLogger, + VERSION_INFO + ) jest.spyOn(anchorService.validator, 'init').mockImplementation(async () => { // Do Nothing @@ -82,7 +88,13 @@ describe('AuthenticatedEthereumAnchorServiceTest', () => { const auth = createDidAnchorServiceAuth(url, ceramic.signer, diagnosticsLogger, fauxFetchJson) const signRequestSpy = jest.spyOn(auth, 'signRequest') - const anchorService = new AuthenticatedEthereumAnchorService(auth, url, url, diagnosticsLogger) + const anchorService = new AuthenticatedEthereumAnchorService( + auth, + url, + url, + diagnosticsLogger, + VERSION_INFO + ) jest.spyOn(anchorService.validator, 'init').mockImplementation(async () => { // Do Nothing }) diff --git a/packages/core/src/anchor/ethereum/__tests__/remote-cas.test.ts b/packages/core/src/anchor/ethereum/__tests__/remote-cas.test.ts index 95a9621cce..3e0633eb5a 100644 --- a/packages/core/src/anchor/ethereum/__tests__/remote-cas.test.ts +++ b/packages/core/src/anchor/ethereum/__tests__/remote-cas.test.ts @@ -10,6 +10,8 @@ import { CID } from 'multiformats/cid' const ANCHOR_SERVICE_URL = 'http://example.com' const POLL_INTERVAL = 100 const LOGGER = new LoggerProvider().getDiagnosticsLogger() +const VERSION_INFO = { cliPackageVersion: '', gitHash: '', ceramicOneVersion: '' } + export const FAKE_STREAM_ID = StreamID.fromString( 'kjzl6cwe1jw147dvq16zluojmraqvwdmbh61dx9e0c59i344lcrsgqfohexp60s' ) @@ -20,7 +22,7 @@ describe('RemoteCAS supportedChains', () => { const fetchFn = jest.fn(async () => ({ supportedChains: ['eip155:42'], })) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) const supportedChains = await cas.supportedChains() expect(supportedChains).toEqual(['eip155:42']) @@ -33,7 +35,7 @@ describe('RemoteCAS supportedChains', () => { someOtherField: 'SomeOtherContent', supportedChains: ['eip155:42'], })) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) const supportedChains = await cas.supportedChains() expect(supportedChains).toEqual(['eip155:42']) expect(fetchFn).toBeCalledTimes(1) @@ -44,7 +46,7 @@ describe('RemoteCAS supportedChains', () => { const fetchFn = jest.fn(async () => ({ supportedChains: ['eip155:42', 'eip155:1'], })) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) await expect(cas.supportedChains()).rejects.toThrow( `SupportedChains response : ${JSON.stringify({ supportedChains: ['eip155:42', 'eip155:1'], @@ -56,7 +58,7 @@ describe('RemoteCAS supportedChains', () => { const fetchFn = jest.fn(async () => ({ incorrectFieldName: ['eip155:42'], })) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) await expect(cas.supportedChains()).rejects.toThrow( `SupportedChains response : ${JSON.stringify({ incorrectFieldName: ['eip155:42'], @@ -68,7 +70,7 @@ describe('RemoteCAS supportedChains', () => { const fetchFnNull = jest.fn(async () => ({ supportedChains: null, })) as unknown as typeof fetchJson - const casForNull = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFnNull) + const casForNull = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFnNull, VERSION_INFO) const expectedErrorNull = 'Error: Invalid value null supplied to /(SupportedChainsResponse)/supportedChains(supportedChains)' await expect(casForNull.supportedChains()).rejects.toThrow( @@ -80,7 +82,7 @@ describe('RemoteCAS supportedChains', () => { const fetchFnEmpty = jest.fn(async () => ({ supportedChains: [], })) as unknown as typeof fetchJson - const casForEmpty = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFnEmpty) + const casForEmpty = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFnEmpty, VERSION_INFO) const expectedErrorUndefined = `Error: Invalid value [] supplied to /(SupportedChainsResponse)/supportedChains(supportedChains)` await expect(casForEmpty.supportedChains()).rejects.toThrow( `SupportedChains response : ${JSON.stringify({ @@ -103,7 +105,7 @@ describe('create', () => { updatedAt: dateAsUnix.encode(new Date()), }) ) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) const result = await cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date()) expect(fetchFn).toBeCalled() expect(result).toEqual({ @@ -118,7 +120,7 @@ describe('create', () => { const fetchFn = jest.fn(async () => { throw new Error(`Oops`) }) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() }) }) @@ -140,7 +142,8 @@ describe('getStatusForRequest', () => { const cas = new RemoteCAS( LOGGER, ANCHOR_SERVICE_URL, - fetchJsonFn as unknown as typeof fetchJson + fetchJsonFn as unknown as typeof fetchJson, + VERSION_INFO ) const response = await cas.getStatusForRequest(streamId, tip) expect(response).toEqual({ @@ -163,7 +166,8 @@ describe('getStatusForRequest', () => { const cas = new RemoteCAS( LOGGER, ANCHOR_SERVICE_URL, - fetchJsonFn as unknown as typeof fetchJson + fetchJsonFn as unknown as typeof fetchJson, + VERSION_INFO ) const responseP = cas.getStatusForRequest(streamId, tip) await TestUtils.delay(POLL_INTERVAL) @@ -191,13 +195,13 @@ describe('assertCASAccessible', () => { } test('Starts accessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) cas.assertCASAccessible() }) describe('create failures', () => { test('Failures without time passing still accessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() @@ -209,7 +213,7 @@ describe('assertCASAccessible', () => { }) test('Time passing without sufficient failures still accessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() @@ -219,7 +223,7 @@ describe('assertCASAccessible', () => { }) test('Time passing plus sufficient failures means inaccessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() @@ -251,7 +255,7 @@ describe('assertCASAccessible', () => { } }) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() @@ -278,7 +282,7 @@ describe('assertCASAccessible', () => { const tip = TestUtils.randomCID() test('Failures without time passing still accessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() @@ -290,7 +294,7 @@ describe('assertCASAccessible', () => { }) test('Time passing without sufficient failures still accessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() @@ -300,7 +304,7 @@ describe('assertCASAccessible', () => { }) test('Time passing plus sufficient failures means inaccessible', async () => { - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() @@ -330,7 +334,7 @@ describe('assertCASAccessible', () => { } }) as unknown as typeof fetchJson - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchFn, VERSION_INFO) await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() @@ -356,7 +360,7 @@ describe('assertCASAccessible', () => { test('Time passing plus sufficient failures means inaccessible', async () => { const streamId = TestUtils.randomStreamID() const tip = TestUtils.randomCID() - const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn) + const cas = new RemoteCAS(LOGGER, ANCHOR_SERVICE_URL, fetchNetworkErrFn, VERSION_INFO) await expect(cas.createRequest(FAKE_STREAM_ID, FAKE_TIP_CID, new Date())).rejects.toThrow() await expect(cas.getStatusForRequest(streamId, tip)).rejects.toThrow() diff --git a/packages/core/src/anchor/ethereum/ethereum-anchor-service.ts b/packages/core/src/anchor/ethereum/ethereum-anchor-service.ts index 6945a1a6d2..ff35a640f1 100644 --- a/packages/core/src/anchor/ethereum/ethereum-anchor-service.ts +++ b/packages/core/src/anchor/ethereum/ethereum-anchor-service.ts @@ -18,6 +18,7 @@ import { doNotWait } from '../../ancillary/do-not-wait.js' import { NamedTaskQueue } from '../../state-management/named-task-queue.js' import { StreamID } from '@ceramicnetwork/streamid' import { CID } from 'multiformats/cid' +import { VersionInfo } from '../../ceramic.js' // BATCH_SIZE controls the number of keys fetched from the AnchorRequestStore at once. // It does not affect the parallelism/concurrency of actually processing the entries in those batches. @@ -48,12 +49,13 @@ export class EthereumAnchorService implements AnchorService { anchorServiceUrl: string, ethereumRpcUrl: string | undefined, logger: DiagnosticsLogger, + versionInfo: VersionInfo, sendRequest: FetchRequest = fetchJson, enableAnchorPollingLoop = true ) { this.#logger = logger this.#events = new Subject() - this.#cas = new RemoteCAS(logger, anchorServiceUrl, sendRequest) + this.#cas = new RemoteCAS(logger, anchorServiceUrl, sendRequest, versionInfo) this.events = this.#events this.url = anchorServiceUrl this.validator = new EthereumAnchorValidator(ethereumRpcUrl, logger) @@ -140,12 +142,14 @@ export class AuthenticatedEthereumAnchorService anchorServiceUrl: string, ethereumRpcUrl: string | undefined, logger: DiagnosticsLogger, + versionInfo: VersionInfo, enableAnchorPollingLoop = true ) { super( anchorServiceUrl, ethereumRpcUrl, logger, + versionInfo, auth.sendAuthenticatedRequest.bind(auth), enableAnchorPollingLoop ) diff --git a/packages/core/src/anchor/ethereum/remote-cas.ts b/packages/core/src/anchor/ethereum/remote-cas.ts index 0a5a03dc1f..ad7d1004a9 100644 --- a/packages/core/src/anchor/ethereum/remote-cas.ts +++ b/packages/core/src/anchor/ethereum/remote-cas.ts @@ -9,6 +9,7 @@ import { deferAbortable } from '../../ancillary/defer-abortable.js' import { catchError, firstValueFrom, Subject, takeUntil, type Observable } from 'rxjs' import { DiagnosticsLogger } from '@ceramicnetwork/common' import { ServiceMetrics as Metrics } from '@ceramicnetwork/observability' +import { VersionInfo } from '../../ceramic.js' const MAX_FAILED_REQUESTS = 3 const MAX_MILLIS_WITHOUT_SUCCESS = 1000 * 60 // 1 minute @@ -69,6 +70,7 @@ export class RemoteCAS implements CASClient { readonly #chainIdApiEndpoint: string readonly #sendRequest: FetchRequest readonly #stopSignal: Subject + readonly #versionInfo: VersionInfo // Used to track when we fail to reach the CAS at all (e.g. from a network error) // Note it does not care about if the status of the request *on* the CAS. In other words, @@ -77,11 +79,17 @@ export class RemoteCAS implements CASClient { #numFailedRequests: number #firstFailedRequestDate: Date | null - constructor(logger: DiagnosticsLogger, anchorServiceUrl: string, sendRequest: FetchRequest) { + constructor( + logger: DiagnosticsLogger, + anchorServiceUrl: string, + sendRequest: FetchRequest, + versionInfo: VersionInfo + ) { this.#logger = logger this.#requestsApiEndpoint = anchorServiceUrl + '/api/v0/requests' this.#chainIdApiEndpoint = anchorServiceUrl + '/api/v0/service-info/supported_chains' this.#sendRequest = sendRequest + this.#versionInfo = versionInfo this.#stopSignal = new Subject() this.#numFailedRequests = 0 this.#firstFailedRequestDate = null @@ -162,6 +170,8 @@ export class RemoteCAS implements CASClient { streamId: streamId.toString(), cid: tip.toString(), timestamp: timestamp.toISOString(), + jsCeramicVersion: this.#versionInfo.cliPackageVersion, + ceramicOneVersion: this.#versionInfo.ceramicOneVersion, }, signal: signal, }) diff --git a/packages/core/src/ceramic.ts b/packages/core/src/ceramic.ts index 5837e325ee..273d7837fa 100644 --- a/packages/core/src/ceramic.ts +++ b/packages/core/src/ceramic.ts @@ -151,9 +151,10 @@ export interface CeramicModules { reconApi: IReconApi } -interface VersionInfo { +export interface VersionInfo { cliPackageVersion: string gitHash: string + ceramicOneVersion: string } /** @@ -367,7 +368,11 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader { * `CeramicModules` from it. This usually should not be called directly - most users will prefer * to call `Ceramic.create()` instead which calls this internally. */ - static _processConfig(ipfs: IpfsApi, config: CeramicConfig): [CeramicModules, CeramicParameters] { + static _processConfig( + ipfs: IpfsApi, + config: CeramicConfig, + versionInfo: VersionInfo + ): [CeramicModules, CeramicParameters] { // Initialize ceramic loggers const loggerProvider = config.loggerProvider ?? new LoggerProvider() const logger = loggerProvider.getDiagnosticsLogger() @@ -385,6 +390,7 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader { ethereumRpcUrl, networkOptions.name, logger, + versionInfo, signer ) const providersCache = new ProvidersCache(ethereumRpcUrl) @@ -444,6 +450,7 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader { loadOptsOverride, sync: config.indexing?.enableHistoricalSync, anchorLoopMinDurationMs: parseInt(config.anchorLoopMinDurationMs, 10), + versionInfo, } const modules: CeramicModules = { @@ -468,9 +475,14 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader { * Create Ceramic instance * @param ipfs - IPFS instance * @param config - Ceramic configuration + * @param versionInfo - Information about the version of js-ceramic and ceramic-one that is being running. */ - static async create(ipfs: IpfsApi, config: CeramicConfig = {}): Promise { - const [modules, params] = Ceramic._processConfig(ipfs, config) + static async create( + ipfs: IpfsApi, + config: CeramicConfig = {}, + versionInfo: VersionInfo + ): Promise { + const [modules, params] = Ceramic._processConfig(ipfs, config, versionInfo) const ceramic = new Ceramic(modules, params) const doPeerDiscovery = config.useCentralizedPeerDiscovery ?? !TESTING @@ -535,11 +547,10 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader { } async _publishVersionMetrics() { - const ipfsVersion = (await this.ipfs.id()).agentVersion Metrics.observe(VERSION_INFO, 1, { jsCeramicVersion: this._versionInfo.cliPackageVersion, jsCeramicGitHash: this._versionInfo.gitHash, - ceramicOneVersion: ipfsVersion, + ceramicOneVersion: this._versionInfo.ceramicOneVersion, }) } diff --git a/packages/core/src/initialization/__tests__/anchoring.test.ts b/packages/core/src/initialization/__tests__/anchoring.test.ts index c880b123cd..b3b1bd1c8c 100644 --- a/packages/core/src/initialization/__tests__/anchoring.test.ts +++ b/packages/core/src/initialization/__tests__/anchoring.test.ts @@ -10,6 +10,11 @@ import { AuthenticatedEthereumAnchorService, EthereumAnchorService, } from '../../anchor/ethereum/ethereum-anchor-service.js' +import { VersionInfo } from '../../ceramic.js' +import { CeramicSigner } from '@ceramicnetwork/common' + +const VERSION_INFO: VersionInfo = { cliPackageVersion: '', gitHash: '', ceramicOneVersion: '' } +const SIGNER = CeramicSigner.invalid() describe('makeAnchorServiceUrl', () => { const CUSTOM_URL = 'https://cas.com' @@ -35,11 +40,25 @@ describe('makeAnchorServiceUrl', () => { describe('makeAnchorService', () => { const logger = new LoggerProvider().getDiagnosticsLogger() test('readOnly means null', () => { - const result = makeAnchorService({ readOnly: true }, '', Networks.MAINNET, logger) + const result = makeAnchorService( + { readOnly: true }, + '', + Networks.MAINNET, + logger, + VERSION_INFO, + SIGNER + ) expect(result).toBeInstanceOf(EthereumAnchorService) }) test('inmemory', () => { - const result = makeAnchorService({ readOnly: false }, '', Networks.INMEMORY, logger) + const result = makeAnchorService( + { readOnly: false }, + '', + Networks.INMEMORY, + logger, + VERSION_INFO, + SIGNER + ) expect(result).toBeInstanceOf(InMemoryAnchorService) }) test('auth', () => { @@ -47,12 +66,21 @@ describe('makeAnchorService', () => { { readOnly: false, anchorServiceAuthMethod: 'auth' }, '', Networks.MAINNET, - logger + logger, + VERSION_INFO, + SIGNER ) expect(result).toBeInstanceOf(AuthenticatedEthereumAnchorService) }) test('no auth', () => { - const result = makeAnchorService({ readOnly: false }, '', Networks.MAINNET, logger) + const result = makeAnchorService( + { readOnly: false }, + '', + Networks.MAINNET, + logger, + VERSION_INFO, + SIGNER + ) expect(result).toBeInstanceOf(EthereumAnchorService) }) }) diff --git a/packages/core/src/initialization/anchoring.ts b/packages/core/src/initialization/anchoring.ts index 21d5c1be82..3636e0cd47 100644 --- a/packages/core/src/initialization/anchoring.ts +++ b/packages/core/src/initialization/anchoring.ts @@ -1,6 +1,6 @@ import { CeramicSigner, type DiagnosticsLogger, Networks } from '@ceramicnetwork/common' import { DIDAnchorServiceAuth } from '../anchor/auth/did-anchor-service-auth.js' -import type { CeramicConfig } from '../ceramic.js' +import type { CeramicConfig, VersionInfo } from '../ceramic.js' import { InMemoryAnchorService } from '../anchor/memory/in-memory-anchor-service.js' import { AuthenticatedEthereumAnchorService, @@ -121,6 +121,7 @@ export function makeAnchorService( ethereumRpcUrl: string | undefined, network: Networks, logger: DiagnosticsLogger, + versionInfo: VersionInfo, signer: CeramicSigner ): AnchorService { if (network === Networks.INMEMORY) { @@ -140,11 +141,12 @@ export function makeAnchorService( anchorServiceAuth, anchorServiceUrl, ethereumRpcUrl, - logger + logger, + versionInfo ) } } - return new EthereumAnchorService(anchorServiceUrl, ethereumRpcUrl, logger) + return new EthereumAnchorService(anchorServiceUrl, ethereumRpcUrl, logger, versionInfo) } const DEFAULT_LOCAL_ETHEREUM_RPC = 'http://localhost:7545' // default Ganache port diff --git a/packages/stream-tests/src/__tests__/ceramic_sync_disabled.test.ts b/packages/stream-tests/src/__tests__/ceramic_sync_disabled.test.ts index 2dd95fdb0a..fa34127645 100644 --- a/packages/stream-tests/src/__tests__/ceramic_sync_disabled.test.ts +++ b/packages/stream-tests/src/__tests__/ceramic_sync_disabled.test.ts @@ -1,5 +1,5 @@ import { jest } from '@jest/globals' -import { Ceramic } from '@ceramicnetwork/core' +import { Ceramic, VersionInfo } from '@ceramicnetwork/core' import { CeramicClient } from '@ceramicnetwork/http-client' import * as tmp from 'tmp-promise' import { CeramicDaemon, DaemonConfig, makeDID } from '@ceramicnetwork/cli' @@ -17,16 +17,20 @@ const makeCeramicCore = async ( stateStoreDirectory: string, disablePeerDataSync: boolean ): Promise => { - const core = await Ceramic.create(ipfs, { - pubsubTopic: TOPIC, - stateStoreDirectory, - anchorOnRequest: false, - indexing: { - disableComposedb: true, + const core = await Ceramic.create( + ipfs, + { + pubsubTopic: TOPIC, + stateStoreDirectory, + anchorOnRequest: false, + indexing: { + disableComposedb: true, + }, + disablePeerDataSync, + anchorLoopMinDurationMs: 0, }, - disablePeerDataSync, - anchorLoopMinDurationMs: 0, - }) + {} as VersionInfo + ) return core } diff --git a/packages/stream-tests/src/create-ceramic.ts b/packages/stream-tests/src/create-ceramic.ts index 581f9d3eb1..adff781502 100644 --- a/packages/stream-tests/src/create-ceramic.ts +++ b/packages/stream-tests/src/create-ceramic.ts @@ -5,6 +5,8 @@ import tmp from 'tmp-promise' import { createDid } from './create_did.js' import type { ProvidersCache } from '@ceramicnetwork/core' +const VERSION_INFO = { cliPackageVersion: '', gitHash: '', ceramicOneVersion: '' } + export async function createCeramic( ipfs: IpfsApi, config: CeramicConfig & { seed?: string } = { networkName: Networks.INMEMORY }, @@ -29,7 +31,7 @@ export async function createCeramic( config ) - const [modules, params] = Ceramic._processConfig(ipfs, appliedConfig) + const [modules, params] = Ceramic._processConfig(ipfs, appliedConfig, VERSION_INFO) if (providersCache) { modules.providersCache = providersCache }