diff --git a/yarn-project/pxe/src/storage/backwards_compatibility_tests/pxe_db_compatibility.test.ts b/yarn-project/pxe/src/storage/backwards_compatibility_tests/pxe_db_compatibility.test.ts index 55db5435c26c..e40e0209e4a1 100644 --- a/yarn-project/pxe/src/storage/backwards_compatibility_tests/pxe_db_compatibility.test.ts +++ b/yarn-project/pxe/src/storage/backwards_compatibility_tests/pxe_db_compatibility.test.ts @@ -1,6 +1,7 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import { KeyStore } from '@aztec/key-store'; +import type { AztecAsyncKVStore } from '@aztec/kv-store'; import { createStore, openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/stdlib/block'; @@ -144,6 +145,42 @@ async function collectOpenedStores() { } } +/** + * Seeds rows into a `pxe_data` store opened at `oldSchemaVersion`, then reopens it at the current + * `PXE_DATA_SCHEMA_VERSION`. The version mismatch makes DatabaseVersionManager wipe the database on open, so + * `assertWiped` runs against a cleared store and can prove the legacy rows are gone. + */ +async function expectStoreWipedOnUpgradeFrom( + oldSchemaVersion: number, + seedLegacyRows: (oldStore: AztecAsyncKVStore) => Promise, + assertWiped: (currentStore: AztecAsyncKVStore) => Promise, +) { + const dataDirectory = await mkdtemp(join(tmpdir(), 'pxe-schema-wipe-')); + const config = { + dataDirectory, + dataStoreMapSizeKb: 1024, + rollupAddress: EthAddress.ZERO, + }; + + try { + const oldStore = await createStore('pxe_data', oldSchemaVersion, config); + try { + await seedLegacyRows(oldStore); + } finally { + await oldStore.close(); + } + + const currentStore = await createStore('pxe_data', PXE_DATA_SCHEMA_VERSION, config); + try { + await assertWiped(currentStore); + } finally { + await currentStore.close(); + } + } finally { + await rm(dataDirectory, { recursive: true, force: true, maxRetries: 3 }); + } +} + /** * Backwards-compatibility test suite for PXE storage. The intent is to detect any change to the bytes PXE writes to * disk that would render existing on-device data unreadable after a version bump. Each schema test (in @@ -163,77 +200,35 @@ async function collectOpenedStores() { describe('PXE storage compatibility test suite', () => { it('wipes key-store rows written before the message-signing and fallback secret keys were removed', async () => { const account = AztecAddress.fromStringUnsafe('0x0b3683ee9df3ed6ed7027145bd6093f783b0bb4d8354501d906db7bb8cb58ea3'); - const dataDirectory = await mkdtemp(join(tmpdir(), 'pxe-schema-reset-')); - const config = { - dataDirectory, - dataStoreMapSizeKb: 1024, - rollupAddress: EthAddress.ZERO, - }; - - try { - const oldStore = await createStore( - 'pxe_data', - PRE_MESSAGE_AND_FALLBACK_SECRET_KEY_REMOVAL_PXE_SCHEMA_VERSION, - config, - ); - try { - await oldStore + await expectStoreWipedOnUpgradeFrom( + PRE_MESSAGE_AND_FALLBACK_SECRET_KEY_REMOVAL_PXE_SCHEMA_VERSION, + oldStore => + oldStore .openMap('key_store') .set( `${account.toString()}-ivsk_m`, Buffer.from('1fb01c42d1aaa2662041b899c77cb19e08192193acc5a94405f1b43c974eba7a', 'hex'), - ); - } finally { - await oldStore.close(); - } - - const currentStore = await createStore('pxe_data', PXE_DATA_SCHEMA_VERSION, config); - try { + ), + async currentStore => { const keyStore = new KeyStore(currentStore); - // Opening a below-current-version DB triggers DatabaseVersionManager to wipe it, so the account written under - // the old schema is gone and the new code never reads its now-incompatible rows. await expect(keyStore.hasAccount(account)).resolves.toBe(false); - } finally { - await currentStore.close(); - } - } finally { - await rm(dataDirectory, { recursive: true, force: true, maxRetries: 3 }); - } + }, + ); }); it('wipes tagging-store rows written under the legacy two-part AppTaggingSecret key format', async () => { - // The legacy unconstrained toString() emitted a two-part `:` key. Build it by hand, since the - // current toString() can only emit the three-part `::` form. These are the exact - // secret/app values the RecipientTaggingStore schema fixture uses, i.e. a real pre-migration key. + // The current toString() only emits the three-part `::` form, so build the legacy two-part + // `:` key by hand. const legacyKey = `${new Fr(2n).toString()}:${AztecAddress.fromBigIntUnsafe(3n).toString()}`; - const dataDirectory = await mkdtemp(join(tmpdir(), 'pxe-schema-tagging-reset-')); - const config = { - dataDirectory, - dataStoreMapSizeKb: 1024, - rollupAddress: EthAddress.ZERO, - }; - - try { - const oldStore = await createStore('pxe_data', PRE_KIND_PREFIXED_TAGGING_KEY_PXE_SCHEMA_VERSION, config); - try { - await oldStore.openMap('highest_aged_index').set(legacyKey, 13); - } finally { - await oldStore.close(); - } - - const currentStore = await createStore('pxe_data', PXE_DATA_SCHEMA_VERSION, config); - try { - // Opening a mismatched-version DB triggers DatabaseVersionManager to wipe it, so the legacy-format key is - // discarded before the new parser (which rejects two-part keys) could ever enumerate it. Assert on the raw - // map: a high-level getter would miss the key regardless of the wipe, because the new three-part - // toString() never reconstructs the legacy key, so it would false-pass. + await expectStoreWipedOnUpgradeFrom( + PRE_KIND_PREFIXED_TAGGING_KEY_PXE_SCHEMA_VERSION, + oldStore => oldStore.openMap('highest_aged_index').set(legacyKey, 13), + async currentStore => { + // Assert on the raw map, not a high-level getter: the new three-part toString() never reconstructs the + // legacy two-part key, so a getter would report it absent (false-pass) whether or not the wipe happened. await expect(currentStore.openMap('highest_aged_index').sizeAsync()).resolves.toBe(0); - } finally { - await currentStore.close(); - } - } finally { - await rm(dataDirectory, { recursive: true, force: true, maxRetries: 3 }); - } + }, + ); }); it('opens the expected set of stores', async () => {