diff --git a/packages/crypto-messages/source/factory.test.ts b/packages/crypto-messages/source/factory.test.ts index 58e2926ec..104d666ba 100644 --- a/packages/crypto-messages/source/factory.test.ts +++ b/packages/crypto-messages/source/factory.test.ts @@ -30,6 +30,7 @@ describe<{ }>("Factory", ({ it, assert, beforeEach }) => { beforeEach(async (context) => { await prepareSandbox(context); + context.app.get(Identifiers.Cryptography.Configuration).setHeight(1); // Required by schema to set number for validators const wallet = {}; const validatorSet = { diff --git a/packages/crypto-messages/source/schemas.test.ts b/packages/crypto-messages/source/schemas.test.ts index 1a347370f..b0f3456a3 100644 --- a/packages/crypto-messages/source/schemas.test.ts +++ b/packages/crypto-messages/source/schemas.test.ts @@ -21,6 +21,7 @@ describe<{ context.app.bind(Identifiers.Cryptography.Configuration).to(Configuration).inSingletonScope(); context.app.get(Identifiers.Cryptography.Configuration).setConfig(cryptoJson); + context.app.get(Identifiers.Cryptography.Configuration).setHeight(1); // Required by schema to set number for validators context.validator = context.app.resolve(Validator); diff --git a/packages/crypto-validation/source/keywords.test.ts b/packages/crypto-validation/source/keywords.test.ts index 04db356dc..5732d8ea7 100644 --- a/packages/crypto-validation/source/keywords.test.ts +++ b/packages/crypto-validation/source/keywords.test.ts @@ -199,15 +199,21 @@ describe<{ .get(Identifiers.Cryptography.Configuration) .getMilestone(1); + // Valid cases let matrix = new Array(roundValidators).fill(true); assert.undefined(context.validator.validate("test", matrix).error); matrix = new Array(roundValidators).fill(false); assert.undefined(context.validator.validate("test", matrix).error); + // We don't check for boolean values, that should be defined at schema level matrix = new Array(roundValidators).fill(1); assert.undefined(context.validator.validate("test", matrix).error); + matrix = new Array(roundValidators).fill("a"); + assert.undefined(context.validator.validate("test", matrix).error); + + // Invalid cases matrix = new Array(roundValidators - 1).fill(false); assert.defined(context.validator.validate("test", matrix).error); @@ -232,15 +238,13 @@ describe<{ .get(Identifiers.Cryptography.Configuration) .getMilestone(1); - let matrix = new Array(roundValidators).fill(true); - assert.undefined(context.validator.validate("test", matrix).error); + for (const minimum of [0, 1, roundValidators - 1, roundValidators]) { + let matrix = new Array(minimum).fill(true); + assert.undefined(context.validator.validate("test", matrix).error); + } - matrix = new Array(roundValidators + 1).fill(true); + let matrix = new Array(roundValidators + 1).fill(true); assert.defined(context.validator.validate("test", matrix).error); - - assert.undefined(context.validator.validate("test", []).error); - assert.undefined(context.validator.validate("test", [false]).error); - assert.undefined(context.validator.validate("test", [true]).error); }); it("keyword isValidatorIndex - should be ok", (context) => { @@ -258,95 +262,11 @@ describe<{ assert.undefined(context.validator.validate("test", index).error); } + assert.defined(context.validator.validate("test", -1).error); assert.defined(context.validator.validate("test", 50.000_01).error); assert.defined(context.validator.validate("test", roundValidators).error); assert.defined(context.validator.validate("test", roundValidators + 1).error); assert.defined(context.validator.validate("test", "a").error); assert.defined(context.validator.validate("test", undefined).error); }); - - it("keyword isValidatorIndex - should be ok for parent height", (context) => { - const schema = { - $id: "test", - type: "object", - properties: { - height: { - type: "integer", - }, - validatorIndex: { isValidatorIndex: {} }, - }, - }; - context.validator.addSchema(schema); - - const { roundValidators } = context.app - .get(Identifiers.Cryptography.Configuration) - .getMilestone(1); - - for (let index = 0; index < roundValidators; index++) { - assert.undefined(context.validator.validate("test", { height: 1, validatorIndex: index }).error); - } - - assert.defined(context.validator.validate("test", { height: 1, validatorIndex: roundValidators }).error); - }); - - it("keyword isValidatorIndex - should be ok for parent block", (context) => { - const schema = { - $id: "test", - type: "object", - properties: { - data: { - type: "object", - properties: { - serialized: { - type: "string", - }, - }, - }, - validatorIndex: { isValidatorIndex: {} }, - }, - }; - context.validator.addSchema(schema); - - let { roundValidators } = context.app - .get(Identifiers.Cryptography.Configuration) - .getMilestone(1); - - const block1 = { - // height=2 - serialized: "000173452bb48901020000000000000000000000000000000", - }; - - for (let index = 0; index < roundValidators; index++) { - assert.undefined(context.validator.validate("test", { data: block1, validatorIndex: index }).error); - } - - assert.defined(context.validator.validate("test", { data: block1, validatorIndex: roundValidators }).error); - - // change milestone to 15 validators at height 15 - context.app - .get(Identifiers.Cryptography.Configuration) - .getMilestones()[2].height = 15; - - context.app - .get(Identifiers.Cryptography.Configuration) - .getMilestones()[2].roundValidators = 15; - - const block2 = { - // height=15 - serialized: "000173452bb489010f0000000000000000000000000000000", - }; - - for (let index = 0; index < 15; index++) { - assert.undefined(context.validator.validate("test", { data: block2, validatorIndex: index }).error); - } - - assert.defined(context.validator.validate("test", { data: block2, validatorIndex: 15 }).error); - - // block 1 still accepted - for (let index = 0; index < roundValidators; index++) { - assert.undefined(context.validator.validate("test", { data: block1, validatorIndex: index }).error); - } - - assert.defined(context.validator.validate("test", { data: block1, validatorIndex: 53 }).error); - }); }); diff --git a/packages/crypto-validation/source/keywords.ts b/packages/crypto-validation/source/keywords.ts index 61cb5d309..00091f366 100644 --- a/packages/crypto-validation/source/keywords.ts +++ b/packages/crypto-validation/source/keywords.ts @@ -1,42 +1,6 @@ import type { Contracts } from "@mainsail/contracts"; import { BigNumber } from "@mainsail/utils"; -import type { AnySchemaObject, FuncKeywordDefinition } from "ajv"; - -const parseBlockNumber = (parentSchema): number | undefined => { - if (!parentSchema || !parentSchema.parentData) { - return undefined; - } - - if (parentSchema.parentData.blockNumber) { - // prevotes / precommits - return parentSchema.parentData.blockNumber; - } - - if (!parentSchema.parentData.data) { - return undefined; - } - - // Proposals contain the block only in serialized form (hex). - // We can extract the block number at a fixed offset here, without needing to deserialize the whole block. - - // See packages/crypto-messages/source/serializer.ts#serializeProposed for reference. - - const serialized = parentSchema.parentData.data.serialized; - if (!serialized) { - return undefined; - } - - if (serialized.length < 30) { - return undefined; - } - - const lockProofSize = 2 + Number.parseInt(serialized.slice(0, 2), 16) * 2; - // version: 1 byte (2 hex) - // timestamp: 6 bytes (12 hex) - // blockNumber: 4 byte (8 hex) - const offset = lockProofSize + 2 + 12; - return Buffer.from(serialized.slice(offset, offset + 8), "hex").readUInt32LE(); -}; +import type { FuncKeywordDefinition } from "ajv"; export const makeKeywords = ( configuration: Contracts.Crypto.Configuration, @@ -59,8 +23,7 @@ export const makeKeywords = ( }; const bignumber: FuncKeywordDefinition = { - // @ts-ignore - compile: (schema) => (data, parentSchema: AnySchemaObject) => { + compile: (schema) => (data) => { const minimum = schema.minimum !== undefined ? schema.minimum : 0; const maximum = schema.maximum !== undefined ? schema.maximum : BigNumber.UINT256_MAX; @@ -95,22 +58,17 @@ export const makeKeywords = ( }, errors: false, keyword: "buffer", - metaSchema: { - type: "object", - }, }; + // Use by: crypto-proposal, p2p const limitToRoundValidators: FuncKeywordDefinition = { - // TODO: Check type (same as bignum) - // @ts-ignore compile(schema: { minimum?: number }) { - return (data, parentSchema: AnySchemaObject) => { + return (data) => { if (!Array.isArray(data)) { return false; } - const blockNumber = parseBlockNumber(parentSchema); - const { roundValidators } = configuration.getMilestone(blockNumber); + const { roundValidators } = configuration.getMilestone(); const minimum = schema.minimum !== undefined ? schema.minimum : roundValidators; if (data.length < minimum || data.length > roundValidators) { @@ -130,26 +88,25 @@ export const makeKeywords = ( }, }; + // Used by: crypto-messages (prevotes / precommits) and crypto-proposal const isValidatorIndex: FuncKeywordDefinition = { - // TODO: Check type (same as bignum) - // @ts-ignore compile() { - return (data, parentSchema: AnySchemaObject) => { - const blockNumber = parseBlockNumber(parentSchema); - const { roundValidators } = configuration.getMilestone(blockNumber); - + return (data) => { if (!Number.isInteger(data)) { return false; } - return data >= 0 && data < roundValidators; + if (data < 0) { + return false; + } + + const { roundValidators } = configuration.getMilestone(); + + return data < roundValidators; }; }, errors: false, keyword: "isValidatorIndex", - metaSchema: { - type: "object", - }, }; return { bignumber, buffer, isValidatorIndex, limitToRoundValidators, maxBytes }; diff --git a/packages/crypto-validation/source/schemas.test.ts b/packages/crypto-validation/source/schemas.test.ts index 1b1765d71..b38c5fc19 100644 --- a/packages/crypto-validation/source/schemas.test.ts +++ b/packages/crypto-validation/source/schemas.test.ts @@ -75,4 +75,46 @@ describe<{ assert.defined(validator.validate("hex", char.repeat(20)).error); } }); + + it("prefixedDataHex - should be ok", ({ validator }) => { + const validValues = ["0x", "0x00", "0x0123", "0x0123456789abcdef"]; + + for (const value of validValues) { + assert.undefined(validator.validate("prefixedDataHex", value).error); + } + }); + + it("prefixedDataHex - should not be ok", ({ validator }) => { + const invalidValues = ["0x0", "0x000", "deadbeef", "0xGG", "0X00"]; + + for (const value of invalidValues) { + assert.defined(validator.validate("prefixedDataHex", value).error); + } + + assert.defined(validator.validate("prefixedDataHex", 123).error); + assert.defined(validator.validate("prefixedDataHex", null).error); + assert.defined(validator.validate("prefixedDataHex").error); + assert.defined(validator.validate("prefixedDataHex", {}).error); + }); + + it("prefixedQuantityHex - should be ok", ({ validator }) => { + const validValues = ["0x0", "0x1", "0x01", "0x123456789abcdef"]; + + for (const value of validValues) { + assert.undefined(validator.validate("prefixedQuantityHex", value).error); + } + }); + + it("prefixedQuantityHex - should not be ok", ({ validator }) => { + const invalidValues = ["0x", "deadbeef", "0xGG", "0X01"]; + + for (const value of invalidValues) { + assert.defined(validator.validate("prefixedQuantityHex", value).error); + } + + assert.defined(validator.validate("prefixedQuantityHex", 123).error); + assert.defined(validator.validate("prefixedQuantityHex", null).error); + assert.defined(validator.validate("prefixedQuantityHex").error); + assert.defined(validator.validate("prefixedQuantityHex", {}).error); + }); }); diff --git a/packages/crypto-validation/source/schemas.ts b/packages/crypto-validation/source/schemas.ts index 9f5aacf0a..b3e53dd9d 100644 --- a/packages/crypto-validation/source/schemas.ts +++ b/packages/crypto-validation/source/schemas.ts @@ -16,6 +16,7 @@ export const schemas: Record<"alphanumeric" | "hex" | "prefixedQuantityHex" | "p pattern: "^0x([0-9a-f]{2})*$", type: "string", }, + // requires at least one hex character after the "0x" prefix prefixedQuantityHex: { $id: "prefixedQuantityHex", pattern: "^0x[0-9a-f]+$", diff --git a/packages/crypto-validation/source/service-provider.test.ts b/packages/crypto-validation/source/service-provider.test.ts index 630aac8c6..4f5defce2 100644 --- a/packages/crypto-validation/source/service-provider.test.ts +++ b/packages/crypto-validation/source/service-provider.test.ts @@ -1,25 +1,22 @@ import { Identifiers } from "@mainsail/constants"; import { Configuration } from "@mainsail/crypto-config"; -import { Validator } from "@mainsail/validation/source/validator"; import cryptoJson from "../../core/bin/config/devnet/core/crypto.json"; import { Application } from "@mainsail/kernel"; +import { Validator } from "@mainsail/validation"; import { describe } from "@mainsail/test-runner"; +import { schemas } from "./schemas"; import { ServiceProvider } from "./service-provider"; describe<{ app: Application; - validator: Partial; + validator: Validator; serviceProvider: ServiceProvider; -}>("ServiceProvider", ({ it, beforeEach, assert, spy }) => { +}>("ServiceProvider", ({ it, beforeEach, assert }) => { beforeEach((context) => { - context.validator = { - addKeyword: () => {}, - addSchema: () => {}, - }; - context.app = new Application(); - context.app.bind(Identifiers.Cryptography.Validator).toConstantValue(context.validator); + context.app.bind(Identifiers.Cryptography.Validator).to(Validator).inSingletonScope(); + context.validator = context.app.get(Identifiers.Cryptography.Validator); context.app.bind(Identifiers.Cryptography.Configuration).to(Configuration).inSingletonScope(); context.app.get(Identifiers.Cryptography.Configuration).setConfig(cryptoJson); @@ -27,12 +24,11 @@ describe<{ }); it("should register", async ({ validator, serviceProvider }) => { - const spyOnExtend = spy(validator, "addKeyword"); - const spyOnAddSchema = spy(validator, "addSchema"); - await assert.resolves(() => serviceProvider.register()); - spyOnExtend.called(); - spyOnAddSchema.called(); + assert.true(validator.hasSchema("alphanumeric")); + assert.true(validator.hasSchema("hex")); + assert.true(validator.hasSchema("prefixedDataHex")); + assert.true(validator.hasSchema("prefixedQuantityHex")); }); });