Skip to content

Commit

Permalink
feat: throw/warn on mismatch of client version and supported version (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nedsalk committed Oct 3, 2023
1 parent 1e496fe commit 1d5d3e1
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/fresh-mails-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/errors": minor
"@fuel-ts/providers": minor
---

Check mismatch of fuel client version and supported version: throw on major/minor mismatch, warn on patch mismatch
1 change: 1 addition & 0 deletions packages/errors/src/error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export enum ErrorCode {
// chain
LATEST_BLOCK_UNAVAILABLE = 'latest-block-unavailable',
ERROR_BUILDING_BLOCK_EXPLORER_URL = 'error-building-block-explorer-url',
UNSUPPORTED_FUEL_CLIENT_VERSION = 'unsupported-fuel-client-version',

// docs
VITEPRESS_PLUGIN_ERROR = 'vitepress-plugin-error',
Expand Down
1 change: 1 addition & 0 deletions packages/providers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@fuel-ts/interfaces": "workspace:*",
"@fuel-ts/math": "workspace:*",
"@fuel-ts/transactions": "workspace:*",
"@fuel-ts/versions": "workspace:*",
"graphql": "^16.6.0",
"graphql-request": "^5.0.0",
"graphql-tag": "^2.12.6",
Expand Down
23 changes: 23 additions & 0 deletions packages/providers/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
InputMessageCoder,
TransactionCoder,
} from '@fuel-ts/transactions';
import { checkFuelCoreVersionCompatibility } from '@fuel-ts/versions';
import { print } from 'graphql';
import { GraphQLClient } from 'graphql-request';
import type { Client } from 'graphql-sse';
Expand Down Expand Up @@ -346,12 +347,34 @@ export default class Provider {
const chain = await this.fetchChain();
const nodeInfo = await this.fetchNode();

Provider.ensureClientVersionIsSupported(nodeInfo);

return {
chain,
nodeInfo,
};
}

private static ensureClientVersionIsSupported(nodeInfo: NodeInfo) {
const { isMajorSupported, isMinorSupported, isPatchSupported, supportedVersion } =
checkFuelCoreVersionCompatibility(nodeInfo.nodeVersion);

if (!isMajorSupported || !isMinorSupported) {
throw new FuelError(
FuelError.CODES.UNSUPPORTED_FUEL_CLIENT_VERSION,
`Fuel client version: ${nodeInfo.nodeVersion}, Supported version: ${supportedVersion}`
);
}

if (!isPatchSupported) {
// eslint-disable-next-line no-console
console.warn(
FuelError.CODES.UNSUPPORTED_FUEL_CLIENT_VERSION,
`The patch versions of the client and sdk differ. Fuel client version: ${nodeInfo.nodeVersion}, Supported version: ${supportedVersion}`
);
}
}

/**
* Create GraphQL client and set operations.
*
Expand Down
79 changes: 79 additions & 0 deletions packages/providers/test/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { expectToThrowFuelError, safeExec } from '@fuel-ts/errors/test-utils';
import { BN, bn } from '@fuel-ts/math';
import type { Receipt } from '@fuel-ts/transactions';
import { InputType, ReceiptType, TransactionType } from '@fuel-ts/transactions';
import * as fuelTsVersionsMod from '@fuel-ts/versions';
import { versions } from '@fuel-ts/versions';

import type { FetchRequestOptions } from '../src/provider';
import Provider from '../src/provider';
Expand All @@ -20,6 +22,12 @@ import { fromTai64ToUnix, fromUnixToTai64 } from '../src/utils';

import { messageProofResponse, messageStatusResponse } from './fixtures';

// https://stackoverflow.com/a/72885576
jest.mock('@fuel-ts/versions', () => ({
__esModule: true,
...jest.requireActual('@fuel-ts/versions'),
}));

afterEach(() => {
jest.restoreAllMocks();
});
Expand Down Expand Up @@ -812,4 +820,75 @@ describe('Provider', () => {
)
);
});

it('throws on difference between major client version and supported major version', async () => {
const { FUEL_CORE } = versions;
const [major, minor, patch] = FUEL_CORE.split('.');
const majorMismatch = major === '0' ? 1 : parseInt(patch, 10) - 1;

const mock = {
isMajorSupported: false,
isMinorSupported: true,
isPatchSupported: true,
supportedVersion: `${majorMismatch}.${minor}.${patch}`,
};

if (mock.supportedVersion === FUEL_CORE) throw new Error();

const spy = jest.spyOn(fuelTsVersionsMod, 'checkFuelCoreVersionCompatibility');
spy.mockImplementationOnce(() => mock);

await expectToThrowFuelError(() => Provider.create(FUEL_NETWORK_URL), {
code: ErrorCode.UNSUPPORTED_FUEL_CLIENT_VERSION,
message: `Fuel client version: ${FUEL_CORE}, Supported version: ${mock.supportedVersion}`,
});
});

it('throws on difference between minor client version and supported minor version', async () => {
const { FUEL_CORE } = versions;
const [major, minor, patch] = FUEL_CORE.split('.');
const minorMismatch = minor === '0' ? 1 : parseInt(patch, 10) - 1;

const mock = {
isMajorSupported: true,
isMinorSupported: false,
isPatchSupported: true,
supportedVersion: `${major}.${minorMismatch}.${patch}`,
};

if (mock.supportedVersion === FUEL_CORE) throw new Error();

const spy = jest.spyOn(fuelTsVersionsMod, 'checkFuelCoreVersionCompatibility');
spy.mockImplementationOnce(() => mock);

await expectToThrowFuelError(() => Provider.create(FUEL_NETWORK_URL), {
code: ErrorCode.UNSUPPORTED_FUEL_CLIENT_VERSION,
message: `Fuel client version: ${FUEL_CORE}, Supported version: ${mock.supportedVersion}`,
});
});

it('warns on difference between patch client version and supported patch version', async () => {
const { FUEL_CORE } = versions;
const [major, minor, patch] = FUEL_CORE.split('.');

const patchMismatch = patch === '0' ? 1 : parseInt(patch, 10) - 1;
const mock = {
isMajorSupported: true,
isMinorSupported: true,
isPatchSupported: false,
supportedVersion: `${major}.${minor}.${patchMismatch}`,
};
if (mock.supportedVersion === FUEL_CORE) throw new Error();

const spy = jest.spyOn(fuelTsVersionsMod, 'checkFuelCoreVersionCompatibility');
spy.mockImplementation(() => mock);

const warnSpy = jest.spyOn(global.console, 'warn');
await Provider.create(FUEL_NETWORK_URL);

expect(warnSpy).toHaveBeenCalledWith(
ErrorCode.UNSUPPORTED_FUEL_CLIENT_VERSION,
`The patch versions of the client and sdk differ. Fuel client version: ${FUEL_CORE}, Supported version: ${mock.supportedVersion}`
);
});
});
1 change: 1 addition & 0 deletions packages/versions/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import * as indexMod from './index';
describe('index.js', () => {
test('should export versions constant', () => {
expect(indexMod.versions).toBeTruthy();
expect(indexMod.checkFuelCoreVersionCompatibility).toBeTruthy();
});
});
1 change: 1 addition & 0 deletions packages/versions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@
import { getSupportedVersions } from './lib/getSupportedVersions';

export const versions = getSupportedVersions();
export * from './lib/checkFuelCoreVersionCompatibility';
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { checkFuelCoreVersionCompatibility } from './checkFuelCoreVersionCompatibility';
import * as getSupportedVersionsMod from './getSupportedVersions';

describe('getDifferenceToUserFuelCoreVersion', () => {
afterAll(() => jest.restoreAllMocks());

it('should validate all possible version mismatches', () => {
const supportedVersion = '0.1.2';

jest.spyOn(getSupportedVersionsMod, 'getSupportedVersions').mockImplementation(() => ({
FUELS: '1', // not under test
FORC: '1', // not under test
FUEL_CORE: supportedVersion,
}));

expect(checkFuelCoreVersionCompatibility('1.1.2')).toEqual({
isMajorSupported: false,
isMinorSupported: true,
isPatchSupported: true,
supportedVersion,
});

expect(checkFuelCoreVersionCompatibility('1.2.2')).toEqual({
isMajorSupported: false,
isMinorSupported: false,
isPatchSupported: true,
supportedVersion,
});

expect(checkFuelCoreVersionCompatibility('1.1.3')).toEqual({
isMajorSupported: false,
isMinorSupported: true,
isPatchSupported: false,
supportedVersion,
});

expect(checkFuelCoreVersionCompatibility('0.2.2')).toEqual({
isMajorSupported: true,
isMinorSupported: false,
isPatchSupported: true,
supportedVersion,
});

expect(checkFuelCoreVersionCompatibility('0.2.3')).toEqual({
isMajorSupported: true,
isMinorSupported: false,
isPatchSupported: false,
supportedVersion,
});

expect(checkFuelCoreVersionCompatibility('0.1.3')).toEqual({
isMajorSupported: true,
isMinorSupported: true,
isPatchSupported: false,
supportedVersion,
});

expect(checkFuelCoreVersionCompatibility('0.1.2')).toEqual({
isMajorSupported: true,
isMinorSupported: true,
isPatchSupported: true,
supportedVersion,
});
});
});
22 changes: 22 additions & 0 deletions packages/versions/src/lib/checkFuelCoreVersionCompatibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import semver from 'semver';

import { getSupportedVersions } from './getSupportedVersions';

export function checkFuelCoreVersionCompatibility(networkVersion: string) {
const { FUEL_CORE: supportedVersion } = getSupportedVersions();

const networkMajor = semver.major(networkVersion);
const networkMinor = semver.minor(networkVersion);
const networkPatch = semver.patch(networkVersion);

const supportedMajor = semver.major(supportedVersion);
const supportedMinor = semver.minor(supportedVersion);
const supportedPatch = semver.patch(supportedVersion);

return {
supportedVersion,
isMajorSupported: networkMajor === supportedMajor,
isMinorSupported: networkMinor === supportedMinor,
isPatchSupported: networkPatch === supportedPatch,
};
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1d5d3e1

Please sign in to comment.