diff --git a/.github/chain-types.md b/.github/chain-types.md new file mode 100644 index 0000000000..0ddb642102 --- /dev/null +++ b/.github/chain-types.md @@ -0,0 +1,10 @@ +--- +title: Outdated chain types +labels: ['ci', '@apps-config'] +--- + +cc @polkadot-js/notifications + +Some configured chains have outdated types and is determined to problematic as an available chain. + +Check the nightly cron output (or via `yarn ci:chainTypes` locally) and disable the chains until the issue is resolved. The output as found from the test includes: diff --git a/.github/workflows/chain-types.yml b/.github/workflows/chain-types.yml new file mode 100644 index 0000000000..b0802f701c --- /dev/null +++ b/.github/workflows/chain-types.yml @@ -0,0 +1,25 @@ +name: Chain type check +on: + schedule: + - cron: '30 0 * * *' + +jobs: + ss58: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: check + env: + CI_LOG: 123 + run: | + yarn install --immutable | grep -v 'YN0013' + yarn ci:chainTypes + + - name: issue + if: ${{ failure() }} + uses: JasonEtco/create-an-issue@v2 + env: + GITHUB_TOKEN: ${{ secrets.GH_PAT_BOT }} + with: + filename: .github/chain-types.md + update_existing: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index aedb705e58..2bfe505097 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,6 +12,6 @@ jobs: repo-token: ${{ secrets.GH_PAT_BOT }} stale-issue-message: 'This issue has been open for 21 days with no activity and is not labelled as an enhancement. It will be closed in 7 days.' stale-issue-label: 'stale' - exempt-issue-labels: '-size-l,-size-m,-size-s,-size-xl,-size-xs,[bug]' + exempt-issue-labels: '-size-l,-size-m,-size-s,-size-xl,-size-xs,[bug],ci' days-before-stale: 21 days-before-close: 7 diff --git a/jest.config.cjs b/jest.config.cjs index 3cc032fcf6..08b54edc40 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -24,6 +24,6 @@ module.exports = { }, setupFilesAfterEnv: ['/jest/jest-setup.ts'], testEnvironment: 'jsdom', - testTimeout: 25000, + testTimeout: 60000, transformIgnorePatterns: ['/node_modules/(?!@polkadot|@babel/runtime/helpers/esm/)'] }; diff --git a/package.json b/package.json index 93a741aa18..04cef06dd4 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "packElectron:linux": "yarn build:release:electron && electron-builder build --linux", "packElectron": "yarn build:release:electron && yarn clean:electronRelease && electron-builder build -mwl", "docs": "echo \"skipping docs\"", + "ci:chainTypes": "polkadot-dev-run-test --silent packages/apps-config/src/ciChainTypes", "clean": "polkadot-dev-clean-build", "clean:electronBuild": "cd packages/apps-electron && polkadot-dev-clean-build", "clean:electronRelease": "cd packages/apps-electron && rm -rf release", diff --git a/packages/apps-config/src/ciChainTypes.spec.ts b/packages/apps-config/src/ciChainTypes.spec.ts new file mode 100644 index 0000000000..bfe546cb99 --- /dev/null +++ b/packages/apps-config/src/ciChainTypes.spec.ts @@ -0,0 +1,73 @@ +// Copyright 2017-2021 @polkadot/apps-config authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { assert, isError, isString } from '@polkadot/util'; +import { fetch } from '@polkadot/x-fetch'; + +import { typesBundle, typesChain } from './api'; +import { createWsEndpoints } from './endpoints'; + +interface Endpoint { + name: string; + ws: string; +} + +interface DnsResponse { + Answer?: { name: string }[]; + Question: { name: string }[]; +} + +const endpoints = createWsEndpoints((k: string, v?: string) => v || k, true) + .filter(({ isDisabled, value }) => !isDisabled && value && isString(value) && !value.startsWith('ws://')) + .map(({ text, value }): Partial => ({ name: text as string, ws: value as string })) + .filter((v): v is Endpoint => !!v.ws); + +describe('--SLOW--: check configured chain connections', (): void => { + endpoints.forEach(({ name, ws }) => + it(`${name} @ ${ws}`, async (): Promise => { + const [,, hostWithPort] = ws.split('/'); + const [host] = hostWithPort.split(':'); + + const response = await fetch(`https://dns.google/resolve?name=${host}`); + const json = await response.json() as DnsResponse; + + assert(json.Answer, `No DNS entry for ${host}`); + + let provider: WsProvider | null = null; + let api: ApiPromise | null = null; + + try { + provider = new WsProvider(ws, false); + api = new ApiPromise({ + provider, + throwOnConnect: true, + typesBundle, + typesChain + }); + + setTimeout((): void => { + provider && provider.connect().catch(() => undefined); + }, 1000); + + await api.isReadyOrError; + } catch (error) { + if (isError(error) && error.message.includes('Cannot construct unknown type')) { + throw error; + } + } finally { + if (provider) { + try { + if (api) { + await api.disconnect(); + } else { + await provider.disconnect(); + } + } catch { + // ignore + } + } + } + }) + ); +}); diff --git a/packages/apps-config/src/endpoints/index.ts b/packages/apps-config/src/endpoints/index.ts index 72293f77cf..4e960b5ed3 100644 --- a/packages/apps-config/src/endpoints/index.ts +++ b/packages/apps-config/src/endpoints/index.ts @@ -12,7 +12,7 @@ import { createTestingRelays } from './testingRelays'; export { CUSTOM_ENDPOINT_KEY } from './development'; -export function createWsEndpoints (t: TFunction): LinkOption[] { +export function createWsEndpoints (t: TFunction, firstOnly?: boolean): LinkOption[] { return [ ...createCustom(t), { @@ -23,7 +23,7 @@ export function createWsEndpoints (t: TFunction): LinkOption[] { textBy: '', value: '' }, - ...createProductionRelays(t), + ...createProductionRelays(t, firstOnly), { isDisabled: false, isHeader: true, @@ -31,7 +31,7 @@ export function createWsEndpoints (t: TFunction): LinkOption[] { textBy: '', value: '' }, - ...createTestingRelays(t), + ...createTestingRelays(t, firstOnly), { isDisabled: false, isHeader: true, @@ -40,7 +40,7 @@ export function createWsEndpoints (t: TFunction): LinkOption[] { textBy: '', value: '' }, - ...createProduction(t), + ...createProduction(t, firstOnly), { isDisabled: false, isHeader: true, @@ -48,7 +48,7 @@ export function createWsEndpoints (t: TFunction): LinkOption[] { textBy: '', value: '' }, - ...createTesting(t), + ...createTesting(t, firstOnly), { isDevelopment: true, isDisabled: false, diff --git a/packages/apps-config/src/endpoints/production.ts b/packages/apps-config/src/endpoints/production.ts index 0d65eae89a..c59a2f7c4d 100644 --- a/packages/apps-config/src/endpoints/production.ts +++ b/packages/apps-config/src/endpoints/production.ts @@ -15,7 +15,7 @@ import { expandEndpoints } from './util'; // value: The actual hosted secure websocket endpoint // alphabetical based on chain name -export function createProduction (t: TFunction): LinkOption[] { +export function createProduction (t: TFunction, firstOnly?: boolean): LinkOption[] { return expandEndpoints(t, [ { dnslink: 'centrifuge', @@ -166,5 +166,5 @@ export function createProduction (t: TFunction): LinkOption[] { DataHighway: 'wss://westlake.datahighway.com' } } - ]); + ], firstOnly); } diff --git a/packages/apps-config/src/endpoints/productionRelays.ts b/packages/apps-config/src/endpoints/productionRelays.ts index 4e773f7cfa..6076fa3e5d 100644 --- a/packages/apps-config/src/endpoints/productionRelays.ts +++ b/packages/apps-config/src/endpoints/productionRelays.ts @@ -8,9 +8,9 @@ import { createKusama } from './productionRelayKusama'; import { createPolkadot } from './productionRelayPolkadot'; import { expandEndpoints } from './util'; -export function createProductionRelays (t: TFunction): LinkOption[] { +export function createProductionRelays (t: TFunction, firstOnly?: boolean): LinkOption[] { return expandEndpoints(t, [ createPolkadot(t), createKusama(t) - ]); + ], firstOnly); } diff --git a/packages/apps-config/src/endpoints/testing.ts b/packages/apps-config/src/endpoints/testing.ts index 8d541f68db..5ccb28999f 100644 --- a/packages/apps-config/src/endpoints/testing.ts +++ b/packages/apps-config/src/endpoints/testing.ts @@ -14,7 +14,7 @@ import { expandEndpoints } from './util'; // text: The text to display on the dropdown // value: The actual hosted secure websocket endpoint -export function createTesting (t: TFunction): LinkOption[] { +export function createTesting (t: TFunction, firstOnly?: boolean): LinkOption[] { return expandEndpoints(t, [ // alphabetical based on chain name, e.g. Amber, Arcadia, Beresheet, ... { @@ -399,5 +399,5 @@ export function createTesting (t: TFunction): LinkOption[] { ZERO: 'wss://alphaville.zero.io' } } - ]); + ], firstOnly); } diff --git a/packages/apps-config/src/endpoints/testingRelays.ts b/packages/apps-config/src/endpoints/testingRelays.ts index a593aca521..6410894f85 100644 --- a/packages/apps-config/src/endpoints/testingRelays.ts +++ b/packages/apps-config/src/endpoints/testingRelays.ts @@ -8,9 +8,9 @@ import { createRococo } from './testingRelayRococo'; import { createWestend } from './testingRelayWestend'; import { expandEndpoints } from './util'; -export function createTestingRelays (t: TFunction): LinkOption[] { +export function createTestingRelays (t: TFunction, firstOnly?: boolean): LinkOption[] { return expandEndpoints(t, [ createRococo(t), createWestend(t) - ]); + ], firstOnly); } diff --git a/packages/apps-config/src/endpoints/util.ts b/packages/apps-config/src/endpoints/util.ts index ca0f2b361d..969e4b93ed 100644 --- a/packages/apps-config/src/endpoints/util.ts +++ b/packages/apps-config/src/endpoints/util.ts @@ -21,7 +21,7 @@ export function expandLinked (input: LinkOption[]): LinkOption[] { }, []); } -export function expandEndpoint (t: TFunction, { dnslink, genesisHash, info, isChild, isDisabled, linked, paraId, providers, teleport, text }: EndpointOption): LinkOption[] { +export function expandEndpoint (t: TFunction, { dnslink, genesisHash, info, isChild, isDisabled, linked, paraId, providers, teleport, text }: EndpointOption, firstOnly?: boolean): LinkOption[] { const base = { genesisHash, info, @@ -32,19 +32,22 @@ export function expandEndpoint (t: TFunction, { dnslink, genesisHash, info, isCh text }; - const result = Object.entries(providers).map(([host, value], index): LinkOption => ({ - ...base, - dnslink: index === 0 ? dnslink : undefined, - isRelay: false, - textBy: t('rpc.hosted.by', 'hosted by {{host}}', { ns: 'apps-config', replace: { host } }), - value - })); + const result = Object + .entries(providers) + .filter((_, index) => !firstOnly || index === 0) + .map(([host, value], index): LinkOption => ({ + ...base, + dnslink: index === 0 ? dnslink : undefined, + isRelay: false, + textBy: t('rpc.hosted.by', 'hosted by {{host}}', { ns: 'apps-config', replace: { host } }), + value + })); if (linked) { const last = result[result.length - 1]; const options: LinkOption[] = []; - linked.forEach((o) => options.push(...expandEndpoint(t, o))); + linked.forEach((o) => options.push(...expandEndpoint(t, o, firstOnly))); last.isRelay = true; last.linked = options; } @@ -52,6 +55,6 @@ export function expandEndpoint (t: TFunction, { dnslink, genesisHash, info, isCh return expandLinked(result); } -export function expandEndpoints (t: TFunction, input: EndpointOption[]): LinkOption[] { - return input.reduce((result: LinkOption[], input) => result.concat(expandEndpoint(t, input)), []); +export function expandEndpoints (t: TFunction, input: EndpointOption[], firstOnly?: boolean): LinkOption[] { + return input.reduce((result: LinkOption[], input) => result.concat(expandEndpoint(t, input, firstOnly)), []); } diff --git a/packages/react-hooks/src/useTeleport.ts b/packages/react-hooks/src/useTeleport.ts index ab9152d476..37ad110755 100644 --- a/packages/react-hooks/src/useTeleport.ts +++ b/packages/react-hooks/src/useTeleport.ts @@ -30,7 +30,7 @@ const DEFAULT_STATE: Teleport = { oneWay: [] }; -const endpoints = createWsEndpoints((k: string, v: string | undefined) => v || k).filter((v): v is ExtLinkOption => !!v.teleport); +const endpoints = createWsEndpoints((k: string, v?: string) => v || k).filter((v): v is ExtLinkOption => !!v.teleport); function extractRelayDestinations (relayGenesis: string, filter: (l: ExtLinkOption) => boolean): ExtLinkOption[] { return endpoints