UNPKG

@kyve/core-beta

Version:

🚀 The base KYVE node implementation.

1,640 lines (1,316 loc) • 57.6 kB
import { Logger } from "tslog"; import { bundleToBytes, ICompression, IStorageProvider, Node, sha256, standardizeJSON, } from "../src/index"; import { runNode } from "../src/methods/main/runNode"; import { genesis_pool } from "./mocks/constants"; 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 { VoteType } from "@kyve/proto-beta/client/kyve/bundles/v1beta1/tx"; import { TestNormalStorageProvider } from "./mocks/storageProvider.mock"; import { TestNormalCompression } from "./mocks/compression.mock"; /* TEST CASES - vote abstain tests * TODO: test if proposed to height is greater than of loadBundle * vote abstain because local bundle could not be loaded at all * vote abstain because local bundle could only be loaded partially * vote abstain because local bundle could only be loaded partially multiple times * vote abstain because bundle from storage provider could not be loaded * vote abstain because bundle from storage provider could not be loaded multiple times * vote abstain because local and storage provider bundle could not be loaded * try to vote abstain after validator has already voted abstain * try to vote abstain after validator has already voted valid * try to vote abstain after validator has already voted invalid * try to vote abstain where voteBundleProposal fails * try to validate bundle but summarizeDataBundle fails * try to validate bundle but validateDataItem fails */ describe("vote abstain tests", () => { let core: Node; let processExit: jest.Mock<never, never>; let setTimeoutMock: jest.Mock; let storageProvider: IStorageProvider; let compression: ICompression; beforeEach(() => { core = new Node(new TestRuntime()); core["cacheProvider"] = new TestCacheProvider(); // mock storage provider storageProvider = new TestNormalStorageProvider(); core["storageProviderFactory"] = jest .fn() .mockResolvedValue(storageProvider); // mock compression compression = new TestNormalCompression(); core["compressionFactory"] = jest.fn().mockReturnValue(compression); // mock process.exit processExit = jest.fn<never, never>(); 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 core.logger = new Logger(); core.logger.info = jest.fn(); core.logger.debug = jest.fn(); core.logger.warn = jest.fn(); core.logger.error = jest.fn(); core["poolId"] = 0; core["staker"] = "test_staker"; core.client = client(); core.lcd = lcd(); core["waitForNextBundleProposal"] = jest.fn(); core["continueRound"] = jest .fn() .mockReturnValueOnce(true) .mockReturnValue(false); setupMetrics.call(core); }); afterEach(() => { // reset prometheus register.clear(); }); test("vote abstain because local bundle could not be loaded at all", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(2, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(1); expect(storageProvider.retrieveBundle).toHaveBeenLastCalledWith( "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(3); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(3, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("vote abstain because local bundle could only be loaded partially", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(2, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(1); expect(storageProvider.retrieveBundle).toHaveBeenLastCalledWith( "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(4); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "1"); expect(cacheProvider.get).toHaveBeenNthCalledWith(3, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(4, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("vote abstain because local bundle could only be loaded partially multiple times", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); // TODO: find out how to properly mock voteBundleProposal result // expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(1); expect(storageProvider.retrieveBundle).toHaveBeenLastCalledWith( "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(5); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(3, "1"); expect(cacheProvider.get).toHaveBeenNthCalledWith(4, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(5, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("vote abstain because bundle from storage provider could not be loaded", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); storageProvider.retrieveBundle = jest .fn() .mockRejectedValueOnce(new Error()) .mockResolvedValue({ storageId: "test_storage_id", storageData: compressedBundle, }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(2, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(2); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 1, "another_test_storage_id", (120 - 20) * 1000 ); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 2, "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(2); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("vote abstain because bundle from storage provider could not be loaded multiple times", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); const retrieveBundleMock = jest .fn() .mockRejectedValueOnce(new Error()) .mockRejectedValueOnce(new Error()) .mockResolvedValue({ storageId: "test_storage_id", storageData: compressedBundle, }); storageProvider.retrieveBundle = retrieveBundleMock; // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); // TODO: find out how to properly mock voteBundleProposal result // expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(3); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 1, "another_test_storage_id", (120 - 20) * 1000 ); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 2, "another_test_storage_id", (120 - 20) * 1000 ); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 3, "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(2); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("vote abstain because local and storage provider bundle could not be loaded", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); storageProvider.retrieveBundle = jest .fn() .mockRejectedValueOnce(new Error()) .mockResolvedValue({ storageId: "test_storage_id", storageData: compressedBundle, }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); // TODO: find out how to properly mock voteBundleProposal result // expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(2); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 1, "another_test_storage_id", (120 - 20) * 1000 ); expect(storageProvider.retrieveBundle).toHaveBeenNthCalledWith( 2, "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(3); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(3, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("try to vote abstain after validator has already voted abstain", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], voters_abstain: ["test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(1); expect(txs.voteBundleProposal).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(1); expect(storageProvider.retrieveBundle).toHaveBeenLastCalledWith( "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(3); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(3, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("try to vote abstain after validator has already voted valid", async () => { // ARRANGE const canVoteMock = jest.fn().mockResolvedValue({ possible: false, reaseon: "already voted valid", }); core.lcd.kyve.query.v1beta1.canVote = canVoteMock; const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker", "test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(0); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(0); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(0); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(0); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(0); expect(runtime.validateDataItem).toHaveBeenCalledTimes(0); // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("try to vote abstain after validator has already voted invalid", async () => { // ARRANGE const canVoteMock = jest.fn().mockResolvedValue({ possible: false, reaseon: "already voted invalid", }); core.lcd.kyve.query.v1beta1.canVote = canVoteMock; const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], voters_invalid: ["test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(0); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(0); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(0); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(0); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(0); expect(runtime.validateDataItem).toHaveBeenCalledTimes(0); // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("try to vote abstain where voteBundleProposal fails", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["client"].kyve.bundles.v1beta1.voteBundleProposal = jest .fn() .mockRejectedValue(new Error()); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal, storage_id: "another_test_storage_id", uploader: "another_test_staker", next_uploader: "another_test_staker", data_size: dataSize, data_hash: dataHash, bundle_size: "2", from_key: "test_key_1", to_key: "test_key_2", bundle_summary: JSON.stringify(bundle), updated_at: "0", voters_valid: ["another_test_staker"], }, } as any; }); core["cacheProvider"].get = jest .fn() .mockRejectedValueOnce(new Error("not found")) .mockResolvedValueOnce({ key: "test_key_1", value: "test_value_1", }) .mockResolvedValueOnce({ key: "test_key_2", value: "test_value_2", }); // ACT await runNode.call(core); // ASSERT const txs = core["client"].kyve.bundles.v1beta1; const queries = core["lcd"].kyve.query.v1beta1; const cacheProvider = core["cacheProvider"]; const runtime = core["runtime"]; // ======================== // ASSERT CLIENT INTERFACES // ======================== expect(txs.claimUploaderRole).toHaveBeenCalledTimes(0); expect(txs.voteBundleProposal).toHaveBeenCalledTimes(2); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(1, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_ABSTAIN, }); expect(txs.voteBundleProposal).toHaveBeenNthCalledWith(2, { staker: "test_staker", pool_id: "0", storage_id: "another_test_storage_id", vote: VoteType.VOTE_TYPE_VALID, }); expect(txs.submitBundleProposal).toHaveBeenCalledTimes(0); expect(txs.skipUploaderRole).toHaveBeenCalledTimes(0); // ===================== // ASSERT LCD INTERFACES // ===================== expect(queries.canVote).toHaveBeenCalledTimes(1); expect(queries.canVote).toHaveBeenLastCalledWith({ staker: "test_staker", pool_id: "0", voter: "test_valaddress", storage_id: "another_test_storage_id", }); expect(queries.canPropose).toHaveBeenCalledTimes(0); // ========================= // ASSERT STORAGE INTERFACES // ========================= expect(storageProvider.saveBundle).toHaveBeenCalledTimes(0); expect(storageProvider.retrieveBundle).toHaveBeenCalledTimes(1); expect(storageProvider.retrieveBundle).toHaveBeenLastCalledWith( "another_test_storage_id", (120 - 20) * 1000 ); // ======================= // ASSERT CACHE INTERFACES // ======================= expect(cacheProvider.get).toHaveBeenCalledTimes(3); expect(cacheProvider.get).toHaveBeenNthCalledWith(1, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(2, "0"); expect(cacheProvider.get).toHaveBeenNthCalledWith(3, "1"); // ============================= // ASSERT COMPRESSION INTERFACES // ============================= expect(compression.compress).toHaveBeenCalledTimes(0); expect(compression.decompress).toHaveBeenCalledTimes(1); expect(compression.decompress).toHaveBeenLastCalledWith(compressedBundle); // ============================= // ASSERT INTEGRATION INTERFACES // ============================= expect(runtime.summarizeDataBundle).toHaveBeenCalledTimes(1); expect(runtime.summarizeDataBundle).toHaveBeenLastCalledWith(bundle); expect(runtime.validateDataItem).toHaveBeenCalledTimes(bundle.length); for (let i = 0; i < bundle.length; i++) { expect(runtime.validateDataItem).toHaveBeenNthCalledWith( i + 1, expect.any(Node), standardizeJSON(bundle[i]), standardizeJSON(bundle[i]) ); } // ======================== // ASSERT NODEJS INTERFACES // ======================== // assert that only one round ran expect(core["waitForNextBundleProposal"]).toHaveBeenCalledTimes(1); // TODO: assert timeouts }); test("try to validate bundle but summarizeDataBundle fails", async () => { // ARRANGE const bundle = [ { key: "test_key_1", value: "test_value_1" }, { key: "test_key_2", value: "test_value_2" }, ]; const bundleBytes = bundleToBytes(bundle); const compressedBundle = bundleBytes; // no compression const dataSize = compressedBundle.byteLength.toString(); const dataHash = sha256(bundleBytes); core["runtime"].summarizeDataBundle = jest .fn() .mockRejectedValue(new Error()); core["syncPoolState"] = jest.fn().mockImplementation(() => { core.pool = { ...genesis_pool, bundle_proposal: { ...genesis_pool.bundle_proposal,