diff --git a/common/protocol/src/methods/validate/validateBundleProposal.ts b/common/protocol/src/methods/validate/validateBundleProposal.ts index e23f3e87..e1970569 100644 --- a/common/protocol/src/methods/validate/validateBundleProposal.ts +++ b/common/protocol/src/methods/validate/validateBundleProposal.ts @@ -158,12 +158,27 @@ export async function validateBundleProposal( this.logger.debug( `this.runtime.validateDataItem($THIS, $PROPOSED_DATA_ITEM, $VALIDATION_DATA_ITEM)` ); - valid = await this.runtime.validateDataItem( + const vote = await this.runtime.validateDataItem( this, proposedBundle[i], validationBundle[i] ); + // vote abstain if data item validation returned abstain + if (vote === VOTE.ABSTAIN) { + const success = await this.voteBundleProposal( + this.pool.bundle_proposal!.storage_id, + VOTE.ABSTAIN + ); + return success; + } + + if (vote === VOTE.VALID) { + valid = true; + } else if (vote === VOTE.INVALID) { + valid = false; + } + // only log if data item validation returned invalid if (!valid) { this.logger.info( diff --git a/common/protocol/src/types/interfaces/runtime.interface.ts b/common/protocol/src/types/interfaces/runtime.interface.ts index 91f0d01b..a65028d7 100644 --- a/common/protocol/src/types/interfaces/runtime.interface.ts +++ b/common/protocol/src/types/interfaces/runtime.interface.ts @@ -103,13 +103,13 @@ export interface IRuntime { * @param {Validator} v the class of @kyvejs/protocol * @param {DataItem} proposedDataItem the data item proposed by the uploader * @param {DataItem} validationDataItem the data item which the validator created himself for validation again the proposed data item - * @return {Promise} returns whether the proposed data item is valid compared to the validation data item + * @return {Promise} returns whether the vote is valid, invalid or abstain compared against the proposed data item */ validateDataItem( v: Validator, proposedDataItem: DataItem, validationDataItem: DataItem - ): Promise; + ): Promise; /** * Gets a formatted value string from a bundle. This produces a "summary" of diff --git a/common/protocol/test/vote_invalid.test.ts b/common/protocol/test/vote_invalid.test.ts index 09745314..cb1b5254 100644 --- a/common/protocol/test/vote_invalid.test.ts +++ b/common/protocol/test/vote_invalid.test.ts @@ -6,6 +6,7 @@ import { Validator, sha256, standardizeJSON, + VOTE, } from "../src/index"; import { runNode } from "../src/methods/main/runNode"; import { genesis_pool } from "./mocks/constants"; @@ -115,7 +116,7 @@ describe("invalid votes tests", () => { test("vote invalid because runtime validate function returns false", async () => { // ARRANGE - const validateBundleMock = jest.fn().mockResolvedValue(false); + const validateBundleMock = jest.fn().mockResolvedValue(VOTE.INVALID); v["runtime"].validateDataItem = validateBundleMock; const bundle = [ @@ -949,7 +950,7 @@ describe("invalid votes tests", () => { test("try to vote invalid after validator has voted abstain bebore", async () => { // ARRANGE - const validateBundleMock = jest.fn().mockResolvedValue(false); + const validateBundleMock = jest.fn().mockResolvedValue(VOTE.INVALID); v["runtime"].validateDataItem = validateBundleMock; @@ -1096,7 +1097,7 @@ describe("invalid votes tests", () => { test("try to vote invalid after validator has voted invalid before", async () => { // ARRANGE - const validateBundleMock = jest.fn().mockResolvedValue(false); + const validateBundleMock = jest.fn().mockResolvedValue(VOTE.INVALID); v["runtime"].validateDataItem = validateBundleMock; @@ -1226,7 +1227,7 @@ describe("invalid votes tests", () => { test("try to vote invalid after validator has voted valid before", async () => { // ARRANGE - const validateBundleMock = jest.fn().mockResolvedValue(false); + const validateBundleMock = jest.fn().mockResolvedValue(VOTE.INVALID); v["runtime"].validateDataItem = validateBundleMock; @@ -1354,7 +1355,7 @@ describe("invalid votes tests", () => { test("vote invalid but local bundle could not be loaded in the first try", async () => { // ARRANGE - v["runtime"].validateDataItem = jest.fn().mockResolvedValue(false); + v["runtime"].validateDataItem = jest.fn().mockResolvedValue(VOTE.INVALID); const bundle = [ { key: "test_key_1", value: "test_value_1" }, @@ -1513,7 +1514,7 @@ describe("invalid votes tests", () => { test("vote invalid but bundle from storage provider could not be loaded in the first try", async () => { // ARRANGE - v["runtime"].validateDataItem = jest.fn().mockResolvedValue(false); + v["runtime"].validateDataItem = jest.fn().mockResolvedValue(VOTE.INVALID); const bundle = [ { key: "test_key_1", value: "test_value_1" }, @@ -1690,7 +1691,7 @@ describe("invalid votes tests", () => { .mockReturnValueOnce(true) .mockReturnValue(false); - v["runtime"].validateDataItem = jest.fn().mockResolvedValue(false); + v["runtime"].validateDataItem = jest.fn().mockResolvedValue(VOTE.INVALID); v["client"][0].kyve.bundles.v1beta1.voteBundleProposal = jest .fn() diff --git a/integrations/tendermint-bsync/src/runtime.ts b/integrations/tendermint-bsync/src/runtime.ts index d145a4c2..3964a776 100644 --- a/integrations/tendermint-bsync/src/runtime.ts +++ b/integrations/tendermint-bsync/src/runtime.ts @@ -1,4 +1,4 @@ -import { DataItem, IRuntime, Validator } from '@kyvejs/protocol'; +import { DataItem, IRuntime, Validator, VOTE } from "@kyvejs/protocol"; import { name, version } from '../package.json'; import axios from 'axios'; @@ -62,11 +62,12 @@ export default class TendermintBSync implements IRuntime { _: Validator, proposedDataItem: DataItem, validationDataItem: DataItem - ): Promise { + ): Promise { // apply equal comparison - return ( - JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem) - ); + if (JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem)) { + return VOTE.VALID + } + return VOTE.INVALID } async summarizeDataBundle(_: Validator, bundle: DataItem[]): Promise { diff --git a/integrations/tendermint-ssync/src/runtime.ts b/integrations/tendermint-ssync/src/runtime.ts index 43b9ebe7..a93a73f6 100644 --- a/integrations/tendermint-ssync/src/runtime.ts +++ b/integrations/tendermint-ssync/src/runtime.ts @@ -1,4 +1,4 @@ -import { DataItem, IRuntime, Validator } from '@kyvejs/protocol'; +import { DataItem, IRuntime, Validator, VOTE } from "@kyvejs/protocol"; import { name, version } from '../package.json'; import axios from 'axios'; @@ -126,11 +126,12 @@ export default class TendermintSSync implements IRuntime { _: Validator, proposedDataItem: DataItem, validationDataItem: DataItem - ): Promise { + ): Promise { // apply equal comparison - return ( - JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem) - ); + if (JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem)) { + return VOTE.VALID + } + return VOTE.INVALID } async summarizeDataBundle(_: Validator, bundle: DataItem[]): Promise { diff --git a/integrations/tendermint/src/runtime.ts b/integrations/tendermint/src/runtime.ts index f5cfbc09..1108f0a4 100644 --- a/integrations/tendermint/src/runtime.ts +++ b/integrations/tendermint/src/runtime.ts @@ -1,4 +1,4 @@ -import { DataItem, IRuntime, Validator } from '@kyvejs/protocol'; +import { DataItem, IRuntime, Validator, VOTE } from '@kyvejs/protocol'; import { name, version } from '../package.json'; import axios from 'axios'; import Ajv from 'ajv'; @@ -182,11 +182,25 @@ export default class Tendermint implements IRuntime { _: Validator, proposedDataItem: DataItem, validationDataItem: DataItem - ): Promise { - // apply equal comparison - return ( - JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem) - ); + ): Promise { + if (JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem)) { + return VOTE.VALID + } + // prevent nondeterministic misbehaviour due to osmosis-1 specific problems + if (validationDataItem.value.block.block.header.chain_id === "osmosis-1") { + _.logger.info("Removing begin_block_events: osmosis-1 identified") + // remove nondeterministic begin_block_events to prevent incorrect invalid vote + delete validationDataItem.value.block_results.begin_block_events; + delete proposedDataItem.value.block_results.begin_block_events; + + if (JSON.stringify(proposedDataItem) === JSON.stringify(validationDataItem)) { + _.logger.warn("Voting abstain: value.block_results.begin_block_events don't match") + // vote abstain if begin_block_events are not equal + return VOTE.ABSTAIN + } + } + // vote invalid if data does not match + return VOTE.INVALID } async summarizeDataBundle(_: Validator, bundle: DataItem[]): Promise {