From b11a9252f7084b80779203613d1d76256a2ec790 Mon Sep 17 00:00:00 2001 From: Fabian Riewe Date: Thu, 5 Oct 2023 16:13:39 +0200 Subject: [PATCH] fix: compression errror returns empty array (#76) * fix: proper error handling when compression fails * fix: vote abstain when decompression fails * fix: updated test cases --- .../methods/validate/saveBundleDecompress.ts | 12 +- .../validate/validateBundleProposal.ts | 12 +- common/protocol/test/compression.test.ts | 123 ++++++++++++++++++ 3 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 common/protocol/test/compression.test.ts diff --git a/common/protocol/src/methods/validate/saveBundleDecompress.ts b/common/protocol/src/methods/validate/saveBundleDecompress.ts index 6add016f..d6c226fc 100644 --- a/common/protocol/src/methods/validate/saveBundleDecompress.ts +++ b/common/protocol/src/methods/validate/saveBundleDecompress.ts @@ -22,21 +22,21 @@ export async function saveBundleDecompress( this.logger.debug(`this.compression.decompress($RAW_STORAGE_DATA)`); - const storageBundle = bytesToBundle( - await compression.decompress(rawStorageData) - ); + const decompressed: Buffer = await compression.decompress(rawStorageData); + + const storageBundle: DataItem[] = bytesToBundle(decompressed); this.logger.info( `Successfully decompressed bundle with Compression:${compression.name}` ); - return standardizeError(storageBundle); + return storageBundle; } catch (err) { this.logger.error( `Could not decompress bundle with Compression. Continuing ...` ); - this.logger.error(standardizeError(err)); + this.logger.error(err); - return []; + throw err; } } diff --git a/common/protocol/src/methods/validate/validateBundleProposal.ts b/common/protocol/src/methods/validate/validateBundleProposal.ts index fda27a66..e23f3e87 100644 --- a/common/protocol/src/methods/validate/validateBundleProposal.ts +++ b/common/protocol/src/methods/validate/validateBundleProposal.ts @@ -137,13 +137,13 @@ export async function validateBundleProposal( // if storage provider result is empty skip runtime validation if (storageProviderResult.byteLength) { - // decompress the bundle with the specified compression type - // and convert the bytes into a JSON format - const proposedBundle = await this.saveBundleDecompress( - storageProviderResult - ); - try { + // decompress the bundle with the specified compression type + // and convert the bytes into a JSON format + const proposedBundle = await this.saveBundleDecompress( + storageProviderResult + ); + // perform custom runtime bundle validation this.logger.debug( `Validating bundle proposal by custom runtime validation` diff --git a/common/protocol/test/compression.test.ts b/common/protocol/test/compression.test.ts new file mode 100644 index 00000000..de996e82 --- /dev/null +++ b/common/protocol/test/compression.test.ts @@ -0,0 +1,123 @@ +import { Logger } from "tslog"; +import { ICompression, IStorageProvider, Validator } from "../src/index"; +import { client } from "./mocks/client.mock"; +import { lcd } from "./mocks/lcd.mock"; +import { TestCacheProvider } from "./mocks/cache.mock"; +import { setupMetrics } from "../src/methods"; +import { register } from "prom-client"; +import { TestRuntime } from "./mocks/runtime.mock"; +import { TestNormalStorageProvider } from "./mocks/storageProvider.mock"; +import { TestNormalCompression } from "./mocks/compression.mock"; +import { Gzip } from "../src/reactors/compression/Gzip"; + +/* + +TEST CASES - compression tests + +* Valid parsing +* Invalid parsing +* Valid gzip compression and decompression + +*/ + +describe("compression tests", () => { + let v: Validator; + + let processExit: jest.Mock; + let setTimeoutMock: jest.Mock; + + let storageProvider: IStorageProvider; + let compression: ICompression; + + beforeEach(() => { + v = new Validator(new TestRuntime()); + + v["cacheProvider"] = new TestCacheProvider(); + + // mock storage provider + storageProvider = new TestNormalStorageProvider(); + v["storageProviderFactory"] = jest.fn().mockReturnValue(storageProvider); + + // mock compression + compression = new TestNormalCompression(); + v["compressionFactory"] = jest.fn().mockReturnValue(compression); + + // mock process.exit + processExit = jest.fn(); + process.exit = processExit; + + // mock setTimeout + setTimeoutMock = jest + .fn() + .mockImplementation( + ( + callback: (args: void) => void, + ms?: number | undefined + ): NodeJS.Timeout => { + callback(); + return null as any; + } + ); + global.setTimeout = setTimeoutMock as any; + + // mock logger + v.logger = new Logger({ minLevel: "warn" }); + + v["poolId"] = 0; + v["staker"] = "test_staker"; + + v["rpc"] = ["http://0.0.0.0:26657"]; + v.client = [client()]; + + v["rest"] = ["http://0.0.0.0:1317"]; + v.lcd = [lcd()]; + + v["continueRound"] = jest + .fn() + .mockReturnValueOnce(true) + .mockReturnValue(false); + + v["waitForCacheContinuation"] = jest.fn(); + + setupMetrics.call(v); + }); + + afterEach(() => { + // reset prometheus + register.clear(); + }); + + test("Valid parsing", async () => { + const fill = + "[{\"key\": \"key1\",\"value\": \"value1\"}, {\"key\": \"key2\",\"value\": \"value2\"}]"; + // using array call for function as it is protected + const parsed = await v["saveBundleDecompress"]( + Buffer.alloc(fill.length, fill) + ); + + expect(parsed).toEqual(JSON.parse(fill)); + expect(parsed.length).toEqual(2); + }); + + test("Invalid parsing", async () => { + const fill = "Invalid JSON"; + + // using array call for function as it is protected + await expect( + v["saveBundleDecompress"](Buffer.alloc(fill.length, fill)) + ).rejects.toThrow(); + }); + + // GZIP + + test("Valid gzip compression and decompression", async () => { + const fill = + "[{\"key\": \"key1\",\"value\": \"value1\"}, {\"key\": \"key2\",\"value\": \"value2\"}]"; + + const gzipper = new Gzip(); + const zipped = await gzipper.compress(Buffer.alloc(fill.length, fill)); + const unzipped = await gzipper.decompress(zipped); + + expect(fill).toEqual(unzipped.toString()); + }); +});