-
Notifications
You must be signed in to change notification settings - Fork 55
fix(dashmate): re-sync stale 3.x Drive and rs-dapi images #3898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
thepastaclaw
wants to merge
4
commits into
dashpay:v3.1-dev
Choose a base branch
from
thepastaclaw:fix-dashmate-rc-image-migration
base: v3.1-dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+392
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
686e995
fix(dashmate): re-sync Drive ABCI & rs-dapi images on 3.0.x → 4.0.0-r…
thepastaclaw a662ca6
test(dashmate): tighten 4.0.0-rc.2 image-resync migration spec
thepastaclaw bce07a2
fix(dashmate): re-key image-resync migration at 4.0.0-rc.3 and guard …
thepastaclaw e11795d
fix(dashmate): also re-sync :3-hotfix Drive ABCI & rs-dapi images
thepastaclaw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
326 changes: 326 additions & 0 deletions
326
packages/dashmate/test/unit/config/configFile/migrate3xTo4xRcImages.spec.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,326 @@ | ||
| import path from 'path'; | ||
| import DefaultConfigs from '../../../../src/config/DefaultConfigs.js'; | ||
| import getBaseConfigFactory from '../../../../configs/defaults/getBaseConfigFactory.js'; | ||
| import getLocalConfigFactory from '../../../../configs/defaults/getLocalConfigFactory.js'; | ||
| import getTestnetConfigFactory from '../../../../configs/defaults/getTestnetConfigFactory.js'; | ||
| import getMainnetConfigFactory from '../../../../configs/defaults/getMainnetConfigFactory.js'; | ||
| import getConfigFileMigrationsFactory from '../../../../configs/getConfigFileMigrationsFactory.js'; | ||
| import migrateConfigFileFactory from '../../../../src/config/configFile/migrateConfigFileFactory.js'; | ||
|
|
||
| // Regression coverage for dashpay/platform#3889 and its rework. | ||
| // | ||
| // A node already on 3.0.1 skips the 3.0.0 migration (semver.gt filter) | ||
| // that used to re-sync Drive ABCI and rs-dapi images. The intervening | ||
| // 3.0.1 / 3.0.2 / 3.1.0 migrations only touched Core / Gateway / Tenderdash | ||
| // `.docker.image` fields. The result: a `dashmate update 3.0.x → 4.0.0-rc.x` | ||
| // kept the protocol-11 images (`dashpay/drive:3`, `dashpay/rs-dapi:3`) | ||
| // and the node crash-looped after protocol 12 activation. | ||
| // | ||
| // The original fix keyed the migration at 4.0.0-rc.2 — the same version | ||
| // dashmate already shipped — so any node already stamped | ||
| // configFormatVersion: "4.0.0-rc.2" short-circuited in migrateConfigFile | ||
| // (fromVersion === toVersion) and never ran the migration. The rework | ||
| // re-keys it at 4.0.0-rc.3 (the next release) and adds a stale-shipped- | ||
| // image predicate to preserve customised images. | ||
| describe('migration 4.0.0-rc.3: re-sync Drive ABCI & rs-dapi images (#3889)', () => { | ||
| const STALE_DRIVE = 'dashpay/drive:3'; | ||
| const STALE_DRIVE_DEV = 'dashpay/drive:3-dev'; | ||
| const STALE_DRIVE_RC = 'dashpay/drive:3-rc'; | ||
| const STALE_DRIVE_HOTFIX = 'dashpay/drive:3-hotfix'; | ||
| const STALE_RS_DAPI = 'dashpay/rs-dapi:3'; | ||
| const STALE_RS_DAPI_DEV = 'dashpay/rs-dapi:3-dev'; | ||
| const STALE_RS_DAPI_RC = 'dashpay/rs-dapi:3-rc'; | ||
| const STALE_RS_DAPI_HOTFIX = 'dashpay/rs-dapi:3-hotfix'; | ||
| const CUSTOM_DRIVE = 'private-registry.internal/drive:patched-v3'; | ||
| const CUSTOM_RS_DAPI = 'my-org/rs-dapi:fork-3'; | ||
|
|
||
| let migrate; | ||
| let expectedDriveImage; | ||
| let expectedRsDapiImage; | ||
|
|
||
| beforeEach(() => { | ||
| // Construct the migration directly (no DI). The full DI container | ||
| // transitively imports `@dashevo/dapi-client`, which can fail to | ||
| // load when `@dashevo/wasm-dpp` hasn't been built; the migration | ||
| // itself only needs the default-config factories. | ||
| const homeDirStub = { | ||
| joinPath: (...segments) => path.join('/tmp/dashmate-spec', ...segments), | ||
| }; | ||
| const getBaseConfig = getBaseConfigFactory(); | ||
| const defaults = new DefaultConfigs([ | ||
| getBaseConfig, | ||
| getLocalConfigFactory(getBaseConfig), | ||
| getTestnetConfigFactory(homeDirStub, getBaseConfig), | ||
| getMainnetConfigFactory(homeDirStub, getBaseConfig), | ||
| ]); | ||
| const getMigrations = getConfigFileMigrationsFactory(homeDirStub, defaults); | ||
| migrate = migrateConfigFileFactory(getMigrations); | ||
|
|
||
| expectedDriveImage = defaults.get('base').get('platform.drive.abci.docker.image'); | ||
| expectedRsDapiImage = defaults.get('base').get('platform.dapi.rsDapi.docker.image'); | ||
| }); | ||
|
|
||
| function buildConfig({ | ||
| network, group, driveImage, rsDapiImage, | ||
| }) { | ||
| return { | ||
| group, | ||
| network, | ||
| platform: { | ||
| enable: true, | ||
| drive: { | ||
| abci: { | ||
| docker: { image: driveImage }, | ||
| }, | ||
| }, | ||
| dapi: { | ||
| rsDapi: { | ||
| docker: { image: rsDapiImage }, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // 3.0.x and 3.1.0 are real-world entry points: a node could be on 3.0.1 | ||
| // (the version that originally skipped the 3.0.0 image re-sync — the | ||
| // direct #3889 repro) or on 3.1.0 (skipping it for the same reason). | ||
| // Either way the new migration must converge them — and crucially, the | ||
| // loop runs every migration with key > fromVersion, so this fires even | ||
| // when toVersion is still 4.0.0-rc.2 (the version currently in | ||
| // packages/dashmate/package.json). | ||
| [ | ||
| { fromVersion: '3.0.1', toVersion: '4.0.0-rc.2' }, | ||
| { fromVersion: '3.1.0', toVersion: '4.0.0-rc.2' }, | ||
| { fromVersion: '3.0.1', toVersion: '4.0.0-rc.3' }, | ||
| { fromVersion: '3.1.0', toVersion: '4.0.0-rc.3' }, | ||
| ].forEach(({ fromVersion, toVersion }) => { | ||
| it(`re-syncs stale drive.abci and dapi.rsDapi images on ${fromVersion} → ${toVersion}`, () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: fromVersion, | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| testnet: buildConfig({ | ||
| network: 'testnet', | ||
| group: 'testnet', | ||
| driveImage: STALE_DRIVE, | ||
| rsDapiImage: STALE_RS_DAPI, | ||
| }), | ||
| mainnet: buildConfig({ | ||
| network: 'mainnet', | ||
| group: 'mainnet', | ||
| driveImage: STALE_DRIVE, | ||
| rsDapiImage: STALE_RS_DAPI, | ||
| }), | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, fromVersion, toVersion); | ||
|
|
||
| ['testnet', 'mainnet'].forEach((name) => { | ||
| const { docker: driveDocker } = migrated.configs[name].platform.drive.abci; | ||
| const { docker: rsDapiDocker } = migrated.configs[name].platform.dapi.rsDapi; | ||
|
|
||
| expect(driveDocker.image).to.equal(expectedDriveImage); | ||
| expect(rsDapiDocker.image).to.equal(expectedRsDapiImage); | ||
|
|
||
| // Pin against the specific regression so the test stays | ||
| // meaningful even if a future default happens to match | ||
| // the stale value again. | ||
| expect(driveDocker.image).to.not.equal(STALE_DRIVE); | ||
| expect(rsDapiDocker.image).to.not.equal(STALE_RS_DAPI); | ||
| }); | ||
|
|
||
| expect(migrated.configFormatVersion).to.equal(toVersion); | ||
| }); | ||
| }); | ||
|
|
||
| // All the prerelease label series the 3.x line shipped derive from | ||
| // semver.prerelease(version)[0] in getBaseConfigFactory.js, producing | ||
| // these stale tags. The hotfix variant in particular came from | ||
| // 3.0.1-hotfix.{1..4} (#3020/#3044/#3055/#3060) and 3.1.0-hotfix.1 — | ||
| // an early oversight in the predicate missed it. | ||
| [ | ||
| { label: 'dev-series', drive: STALE_DRIVE_DEV, rsDapi: STALE_RS_DAPI_DEV }, | ||
| { label: 'rc-series', drive: STALE_DRIVE_RC, rsDapi: STALE_RS_DAPI_RC }, | ||
| { label: 'hotfix-series', drive: STALE_DRIVE_HOTFIX, rsDapi: STALE_RS_DAPI_HOTFIX }, | ||
| ].forEach(({ label, drive, rsDapi }) => { | ||
| it(`re-syncs the ${label} stale tags (${drive}, ${rsDapi})`, () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: '3.1.0', | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| testnet: buildConfig({ | ||
| network: 'testnet', | ||
| group: 'testnet', | ||
| driveImage: drive, | ||
| rsDapiImage: rsDapi, | ||
| }), | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, '3.1.0', '4.0.0-rc.3'); | ||
|
|
||
| expect(migrated.configs.testnet.platform.drive.abci.docker.image) | ||
| .to.equal(expectedDriveImage); | ||
| expect(migrated.configs.testnet.platform.dapi.rsDapi.docker.image) | ||
| .to.equal(expectedRsDapiImage); | ||
| }); | ||
| }); | ||
|
|
||
| // The exact case the rework targets: a node that already upgraded to | ||
| // 4.0.0-rc.2 binary kept the stale dashpay/drive:3 / dashpay/rs-dapi:3 | ||
| // tags and got configFormatVersion: "4.0.0-rc.2" stamped on disk. | ||
| // After a chore(release) bumps packages/dashmate/package.json to | ||
| // 4.0.0-rc.3, the next dashmate load enters migrateConfigFile with | ||
| // fromVersion=4.0.0-rc.2 / toVersion=4.0.0-rc.3, and the new | ||
| // migration finally runs (the original 4.0.0-rc.2 key would have | ||
| // been short-circuited by semver.gt('4.0.0-rc.2', '4.0.0-rc.2')). | ||
| it('re-syncs already-stamped 4.0.0-rc.2 configs on the rc.3 release bump', () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: '4.0.0-rc.2', | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| mainnet: buildConfig({ | ||
| network: 'mainnet', | ||
| group: 'mainnet', | ||
| driveImage: STALE_DRIVE, | ||
| rsDapiImage: STALE_RS_DAPI, | ||
| }), | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, '4.0.0-rc.2', '4.0.0-rc.3'); | ||
|
|
||
| expect(migrated.configs.mainnet.platform.drive.abci.docker.image) | ||
| .to.equal(expectedDriveImage); | ||
| expect(migrated.configs.mainnet.platform.dapi.rsDapi.docker.image) | ||
| .to.equal(expectedRsDapiImage); | ||
| expect(migrated.configFormatVersion).to.equal('4.0.0-rc.3'); | ||
| }); | ||
|
|
||
| // Documents the bound of what migration alone can fix: as long as | ||
| // packages/dashmate/package.json sits at 4.0.0-rc.2, a config already | ||
| // stamped 4.0.0-rc.2 short-circuits in migrateConfigFile before any | ||
| // entry can run. This is the structural limitation that motivated | ||
| // re-keying the migration at the *next* release rather than reusing | ||
| // 4.0.0-rc.2 — and why the full rollout depends on a follow-up | ||
| // chore(release) PR bumping the package version (same flow as the | ||
| // 3.0.2 Envoy CVE: #3794 added the migration, #3796 cut the release). | ||
| it('cannot reach a 4.0.0-rc.2 stale config while toVersion is still 4.0.0-rc.2 (release-bump prerequisite)', () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: '4.0.0-rc.2', | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| mainnet: buildConfig({ | ||
| network: 'mainnet', | ||
| group: 'mainnet', | ||
| driveImage: STALE_DRIVE, | ||
| rsDapiImage: STALE_RS_DAPI, | ||
| }), | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, '4.0.0-rc.2', '4.0.0-rc.2'); | ||
|
|
||
| // No migration runs — config returned untouched. | ||
| expect(migrated.configs.mainnet.platform.drive.abci.docker.image).to.equal(STALE_DRIVE); | ||
| expect(migrated.configs.mainnet.platform.dapi.rsDapi.docker.image).to.equal(STALE_RS_DAPI); | ||
| expect(migrated.configFormatVersion).to.equal('4.0.0-rc.2'); | ||
| }); | ||
|
|
||
| // Custom-image preservation. The original 4.0.0-rc.2 migration | ||
| // unconditionally overwrote every drive/rsDapi image with the | ||
| // current default, clobbering private forks, vendor-patched builds, | ||
| // and `:latest` pins. The rework matches only the stale shipped | ||
| // defaults — same style as the 3.0.2 Envoy CVE migration that | ||
| // rewrote only `dashpay/envoy:1.30.*`. | ||
| it('preserves custom Drive / rs-dapi images', () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: '3.1.0', | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| mainnet: buildConfig({ | ||
| network: 'mainnet', | ||
| group: 'mainnet', | ||
| driveImage: CUSTOM_DRIVE, | ||
| rsDapiImage: CUSTOM_RS_DAPI, | ||
| }), | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, '3.1.0', '4.0.0-rc.3'); | ||
|
|
||
| expect(migrated.configs.mainnet.platform.drive.abci.docker.image).to.equal(CUSTOM_DRIVE); | ||
| expect(migrated.configs.mainnet.platform.dapi.rsDapi.docker.image).to.equal(CUSTOM_RS_DAPI); | ||
| }); | ||
|
|
||
| // Mixed config: one config has stale dashmate defaults (gets rewritten), | ||
| // a sibling has a custom image (preserved). Exercises the same predicate | ||
| // pattern as the 3.0.2 Envoy CVE migration end-to-end. | ||
| it('only rewrites the stale config when siblings have custom images', () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: '3.1.0', | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| testnet: buildConfig({ | ||
| network: 'testnet', | ||
| group: 'testnet', | ||
| driveImage: STALE_DRIVE, | ||
| rsDapiImage: STALE_RS_DAPI, | ||
| }), | ||
| mainnet: buildConfig({ | ||
| network: 'mainnet', | ||
| group: 'mainnet', | ||
| driveImage: CUSTOM_DRIVE, | ||
| rsDapiImage: CUSTOM_RS_DAPI, | ||
| }), | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, '3.1.0', '4.0.0-rc.3'); | ||
|
|
||
| expect(migrated.configs.testnet.platform.drive.abci.docker.image) | ||
| .to.equal(expectedDriveImage); | ||
| expect(migrated.configs.testnet.platform.dapi.rsDapi.docker.image) | ||
| .to.equal(expectedRsDapiImage); | ||
|
|
||
| expect(migrated.configs.mainnet.platform.drive.abci.docker.image).to.equal(CUSTOM_DRIVE); | ||
| expect(migrated.configs.mainnet.platform.dapi.rsDapi.docker.image).to.equal(CUSTOM_RS_DAPI); | ||
| }); | ||
|
|
||
| it('no-ops on configs without platform.dapi.rsDapi', () => { | ||
| const rawConfigFile = { | ||
| configFormatVersion: '3.1.0', | ||
| defaultConfigName: null, | ||
| defaultGroupName: null, | ||
| configs: { | ||
| mainnet: { | ||
| group: 'mainnet', | ||
| network: 'mainnet', | ||
| platform: { | ||
| enable: true, | ||
| drive: { | ||
| abci: { | ||
| docker: { image: STALE_DRIVE }, | ||
| }, | ||
| }, | ||
| dapi: {}, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| const migrated = migrate(rawConfigFile, '3.1.0', '4.0.0-rc.3'); | ||
|
|
||
| expect(migrated.configs.mainnet.platform.drive.abci.docker.image) | ||
| .to.equal(expectedDriveImage); | ||
| expect(migrated.configs.mainnet.platform.dapi.rsDapi).to.equal(undefined); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.