Skip to content

Commit

Permalink
feat: verify ceramic-one network matches js-ceramic at startup (#3259)
Browse files Browse the repository at this point in the history
* feat: verify that c1 and js-ceramic are using the same network. we only fail to start if c1 returns a response and we don't match. if it is an error like a 404, we log a warning and proceed assuming it's a version mismatch
  • Loading branch information
dav1do authored Jul 26, 2024
1 parent 3a3bb99 commit 1b1dc49
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 15 deletions.
9 changes: 5 additions & 4 deletions packages/core/src/__tests__/recon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ describe('ReconApi', () => {
)
await reconApi.init()
await firstValueFrom(race(reconApi, timer(1000)))
expect(mockSendRequest).toHaveBeenCalledTimes(1)
// interest + network check
expect(mockSendRequest).toHaveBeenCalledTimes(2)
})

test('should register interests on init', async () => {
Expand All @@ -92,9 +93,9 @@ describe('ReconApi', () => {
const fakeInterest0 = TestUtils.randomStreamID()
const fakeInterest1 = TestUtils.randomStreamID()
await reconApi.init('testInitialCursor', [fakeInterest0, fakeInterest1])
expect(mockSendRequest).toHaveBeenCalledTimes(3)
expect(mockSendRequest.mock.calls[1][0]).toContain(fakeInterest0.toString())
expect(mockSendRequest.mock.calls[2][0]).toContain(fakeInterest1.toString())
expect(mockSendRequest).toHaveBeenCalledTimes(4)
expect(mockSendRequest.mock.calls[2][0]).toContain(fakeInterest0.toString())
expect(mockSendRequest.mock.calls[3][0]).toContain(fakeInterest1.toString())
reconApi.stop()
})
})
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/ceramic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader {
url: ipfs.config.get('Addresses.API').then((url) => url.toString()),
// TODO: WS1-1487 not an official ceramic config option
feedEnabled: config.reconFeedEnabled ?? true,
network: networkOptions.name,
},
logger
)
Expand Down Expand Up @@ -524,7 +525,6 @@ export class Ceramic implements StreamReaderWriter, StreamStateLoader {
async _init(doPeerDiscovery: boolean): Promise<void> {
try {
if (EnvironmentUtils.useRustCeramic()) {
// this is potentially incorrect if we're running in remote mode as we don't control the network and could mismatch with c1
this._logger.imp(
`Connecting to ceramic network '${this._networkOptions.name}' using ceramic-one with Recon for data synchronization.`
)
Expand Down
44 changes: 41 additions & 3 deletions packages/core/src/recon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import {
TeardownLogic,
} from 'rxjs'

import { DiagnosticsLogger, FetchRequest, fetchJson, AbortOptions } from '@ceramicnetwork/common'
import {
DiagnosticsLogger,
FetchRequest,
fetchJson,
AbortOptions,
Networks,
} from '@ceramicnetwork/common'
import { StreamID } from '@ceramicnetwork/streamid'
import { Model } from '@ceramicnetwork/stream-model'
import { CID } from 'multiformats/cid'
Expand All @@ -34,6 +40,8 @@ export type ReconApiConfig = {
url: string | Promise<string>
// Whether the event feed is enabled
feedEnabled: boolean
/// which network are we configured to use
network: Networks
}

/**
Expand Down Expand Up @@ -108,6 +116,7 @@ export class ReconApi extends Observable<ReconEventFeedResponse> implements IRec
}

this.#url = await this.#config.url
await this.verifyNetwork()
await this.registerInterest(Model.MODEL)

for (const interest of initialInterests) {
Expand All @@ -119,6 +128,36 @@ export class ReconApi extends Observable<ReconEventFeedResponse> implements IRec
}
}

private async verifyNetwork(): Promise<void> {
let response
try {
response = await this.#sendRequest(this.#url + '/ceramic/config/network', {
method: 'GET',
})
} catch (err) {
this.#logger.warn(
`Recon: failed to verify network with error ${err}. This is likely due to an older version of ceramic-one and you should upgrade.`
)
return
}
if (response?.name) {
// this works for all types but is a bit odd for local which is 'local-X'
// where X is the --local-network-id. As we either started the binary (in tests) and passed
// in the network, or don't use it at all and just rely on c1, we're okay ignoring that piece
if (!response.name.includes(this.#config.network)) {
throw new Error(
`Recon: failed to verify network as js-ceramic is using ${this.#config.network
} but ceramic-one is on ${response.name
}. Pass --network to the js-ceramic or ceramic-one daemon to make them match.`
)
}
} else {
this.#logger.warn(
`Recon: failed to verify network as nothing was found. This is likely due to an older version of ceramic-one and you should upgrade.`
)
}
}

/**
* Registers interest in a model
* @param model stream id of the model to register interest in
Expand Down Expand Up @@ -229,8 +268,7 @@ export class ReconApi extends Observable<ReconEventFeedResponse> implements IRec
retry({
delay: (err) => {
this.#logger.warn(
`Recon: event feed failed, due to error ${err}; attempting to retry in ${
this.#pollInterval
`Recon: event feed failed, due to error ${err}; attempting to retry in ${this.#pollInterval
}ms`
)
return timer(this.#pollInterval)
Expand Down
14 changes: 7 additions & 7 deletions packages/ipfs-topology/src/ipfs-topology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,28 @@ const BOOTSTRAP_LIST = (ceramicNetwork: Networks): Array<Multiaddr> | null => {
case Networks.MAINNET:
return [
multiaddr(
'/dns4/bootstrap-mainnet-rust-ceramic-1.3box.io/tcp/4101/p2p/12D3KooWJC1yR4KiCnocV9kuAEwtsMNh7Xmu2vzqpBvk2o3MrYd6',
'/dns4/bootstrap-mainnet-rust-ceramic-1.3box.io/tcp/4101/p2p/12D3KooWJC1yR4KiCnocV9kuAEwtsMNh7Xmu2vzqpBvk2o3MrYd6'
),
multiaddr(
'/dns4/bootstrap-mainnet-rust-ceramic-2.3box.io/tcp/4101/p2p/12D3KooWCuS388c1im7KkmdrpsLMziihF8mbcv2w6HPCp4Qmww6m',
'/dns4/bootstrap-mainnet-rust-ceramic-2.3box.io/tcp/4101/p2p/12D3KooWCuS388c1im7KkmdrpsLMziihF8mbcv2w6HPCp4Qmww6m'
),
]
case Networks.TESTNET_CLAY:
return [
multiaddr(
'/dns4/bootstrap-tnet-rust-ceramic-1.3box.io/tcp/4101/p2p/12D3KooWMqCFj5bnwuNi6D6KLhYiK4C8Eh9xSUKv2E6Jozs4nWEE',
'/dns4/bootstrap-tnet-rust-ceramic-1.3box.io/tcp/4101/p2p/12D3KooWMqCFj5bnwuNi6D6KLhYiK4C8Eh9xSUKv2E6Jozs4nWEE'
),
multiaddr(
'/dns4/bootstrap-tnet-rust-ceramic-2.3box.io/tcp/4101/p2p/12D3KooWPFGbRHWfDaWt5MFFeqAHBBq3v5BqeJ4X7pmn2V1t6uNs',
'/dns4/bootstrap-tnet-rust-ceramic-2.3box.io/tcp/4101/p2p/12D3KooWPFGbRHWfDaWt5MFFeqAHBBq3v5BqeJ4X7pmn2V1t6uNs'
),
]
case Networks.DEV_UNSTABLE:
return [
multiaddr(
'/dns4/bootstrap-devqa-rust-ceramic-1.3box.io/tcp/4101/p2p/12D3KooWJmYPnXgst4gW5GoyAYzRB3upLgLVR1oDVGwjiS9Ce7sA',
'/dns4/bootstrap-devqa-rust-ceramic-1.3box.io/tcp/4101/p2p/12D3KooWJmYPnXgst4gW5GoyAYzRB3upLgLVR1oDVGwjiS9Ce7sA'
),
multiaddr(
'/dns4/bootstrap-devqa-rust-ceramic-2.3box.io/tcp/4101/p2p/12D3KooWFCf7sKeW8NHoT35EutjJX5vCpPekYqa4hB4tTUpYrcam',
'/dns4/bootstrap-devqa-rust-ceramic-2.3box.io/tcp/4101/p2p/12D3KooWFCf7sKeW8NHoT35EutjJX5vCpPekYqa4hB4tTUpYrcam'
),
]
case Networks.LOCAL:
Expand Down Expand Up @@ -106,7 +106,7 @@ export class IpfsTopology {
readonly ceramicNetwork: string,
readonly logger: DiagnosticsLogger,
readonly period: number = DEFAULT_BOOTSTRAP_CONNECTION_PERIOD
) { }
) {}

async forceConnection(): Promise<void> {
const bootstrapList: Multiaddr[] = BOOTSTRAP_LIST(this.ceramicNetwork as Networks) || []
Expand Down

0 comments on commit 1b1dc49

Please sign in to comment.