Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
bf8424d
Split to serializeProposal and serializeProposalUnsigned
sebastijankuzner Mar 3, 2026
6e21a92
Rename proposedData
sebastijankuzner Mar 3, 2026
51cc56d
Extract unsignedProposalSize
sebastijankuzner Mar 3, 2026
4629e11
Fix serializer tests
sebastijankuzner Mar 3, 2026
3a19d10
Edit fixtures
sebastijankuzner Mar 3, 2026
1204ca6
Rename to serializeProposedData
sebastijankuzner Mar 3, 2026
03a3501
Test serializeProposedData
sebastijankuzner Mar 3, 2026
2102717
Rename field
sebastijankuzner Mar 3, 2026
872ee7c
Fix test
sebastijankuzner Mar 3, 2026
dfd4cc9
Test service provider
sebastijankuzner Mar 3, 2026
2c276a7
Move buffer keyword from p2p to crypto-validation
sebastijankuzner Mar 3, 2026
6124256
Merge branch 'develop' into test/crypto-proposal/coverage
sebastijankuzner Mar 4, 2026
d773e7b
Move keywords to crypto-validation
sebastijankuzner Mar 4, 2026
9fec70c
Fix tests
sebastijankuzner Mar 4, 2026
4c7fc36
Renames
sebastijankuzner Mar 4, 2026
2a08695
Organize fixtures
sebastijankuzner Mar 4, 2026
a5ae43d
Rename
sebastijankuzner Mar 4, 2026
cf92ef3
Disable tests
sebastijankuzner Mar 4, 2026
62cab7b
Edit contracts
sebastijankuzner Mar 4, 2026
267654d
Rename data to payload
sebastijankuzner Mar 4, 2026
e148637
Prepare proposal data
sebastijankuzner Mar 4, 2026
03b7786
Add serializer tests
sebastijankuzner Mar 4, 2026
5ce4e83
Test deserializer
sebastijankuzner Mar 4, 2026
e736ba4
Add proposal with valid round
sebastijankuzner Mar 4, 2026
f329725
Serialize block
sebastijankuzner Mar 5, 2026
2dba337
Fix proposal
sebastijankuzner Mar 5, 2026
c5ae0b6
Fix block
sebastijankuzner Mar 5, 2026
fd374fa
Test factory
sebastijankuzner Mar 5, 2026
5e4d000
Update
sebastijankuzner Mar 5, 2026
8166f8c
Fix proposal with valid round
sebastijankuzner Mar 5, 2026
6c89a4c
Add ProposalWithLockProof
sebastijankuzner Mar 5, 2026
1bb9fbe
Add ProposalWithLockProofAndValidRound
sebastijankuzner Mar 5, 2026
b831676
Cleanup
sebastijankuzner Mar 5, 2026
c876583
style: resolve style guide violations
sebastijankuzner Mar 5, 2026
045f3ee
Merge branch 'develop' into test/crypto-proposal/coverage
sebastijankuzner Mar 10, 2026
8b77b7a
Test proposal
sebastijankuzner Mar 11, 2026
d8680b5
Test proposal
sebastijankuzner Mar 11, 2026
b8a6a88
Rename to deserializePayload
sebastijankuzner Mar 11, 2026
ed51bc0
Improve factory layout
sebastijankuzner Mar 11, 2026
8749dc3
Remove makeProposalFromData
sebastijankuzner Mar 11, 2026
2b88a0b
Test schema
sebastijankuzner Mar 11, 2026
2eb125c
Verify lockProof
sebastijankuzner Mar 11, 2026
a926de4
style: resolve style guide violations
sebastijankuzner Mar 11, 2026
fc769ce
style: resolve style guide violations
ItsANameToo Mar 11, 2026
c9d2ec1
Test lock proof
sebastijankuzner Mar 11, 2026
7f1e2f7
Test proposal schema
sebastijankuzner Mar 11, 2026
3c5b979
Use payloadSerialized
sebastijankuzner Mar 11, 2026
5c2f666
Fix serializer
sebastijankuzner Mar 11, 2026
dc126dc
style: resolve style guide violations
sebastijankuzner Mar 11, 2026
505e1b0
Merge branch 'develop' into test/crypto-proposal/coverage
sebastijankuzner Mar 12, 2026
bc8d43c
Test proposal schema
sebastijankuzner Mar 12, 2026
408ac43
Remove support tests
sebastijankuzner Mar 12, 2026
2c16c53
Test proposal
sebastijankuzner Mar 12, 2026
8724f72
Verify unsigned data
sebastijankuzner Mar 12, 2026
c92b4f1
Edit fixtures
sebastijankuzner Mar 12, 2026
400717d
Edit serializer schema
sebastijankuzner Mar 12, 2026
2355997
Fix tests
sebastijankuzner Mar 12, 2026
2a793bb
Generate fixtures script
sebastijankuzner Mar 12, 2026
2f6c9db
Add headerFromBytes
sebastijankuzner Mar 12, 2026
d6ae039
Use factory
sebastijankuzner Mar 12, 2026
6639d31
Check trailing bytes
sebastijankuzner Mar 12, 2026
44a80bd
style: resolve style guide violations [ci-lint-fix]
sebastijankuzner Mar 12, 2026
08be08b
Update dependencies
sebastijankuzner Mar 12, 2026
bcb6e7c
Fix p2p tests
sebastijankuzner Mar 13, 2026
a615ec4
Fix consensus tests
sebastijankuzner Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/consensus/source/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ export class Consensus implements Contracts.Consensus.Service {
const proposal = roundState.getProposal();
if (!roundState.hasProcessorResult() && proposal) {
try {
await proposal.deserializeData();
await proposal.deserializePayload();

if (!(await this.proposalProcessor.hasValidLockProof(proposal))) {
roundState.setProcessorResult(FAILED_PROCESSOR_RESULT);
Expand Down
2 changes: 1 addition & 1 deletion packages/consensus/source/processors/proposal-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class ProposalProcessor extends AbstractProcessor implements Contracts.Co
async #hasValidSignature(proposal: Contracts.Crypto.Proposal): Promise<boolean> {
return this.consensusSignature.verify(
Buffer.from(proposal.signature, "hex"),
await this.proposalSerializer.serializeProposal(proposal.toSerializableData(), { includeSignature: false }),
await this.proposalSerializer.serializeProposalUnsigned(proposal.toSerializableData()),
Buffer.from(this.validatorSet.getValidator(proposal.validatorIndex).blsPublicKey, "hex"),
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/consensus/source/round-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class RoundState implements Contracts.Consensus.RoundState {

public getBlock(): Contracts.Crypto.Block {
if (this.#proposal && this.#proposal.isDataDeserialized) {
return this.#proposal.getData().block;
return this.#proposal.getPayload().block;
}

throw new Error("Block is not available, because proposal is not set or deserialized");
Expand All @@ -117,7 +117,7 @@ export class RoundState implements Contracts.Consensus.RoundState {
assert.defined(proposal);

const round = proposal.round;
const block = proposal.getData().block;
const block = proposal.getPayload().block;

const commit: Contracts.Crypto.CommitSerializable = {
block,
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/source/contracts/crypto/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface BlockFactory {
fromBytes(buff: Buffer): Promise<Block>;
fromJson(json: BlockJson): Promise<Block>;
fromStorage(header: BlockHeaderStorageData, transactions: TransactionStorageData[]): Promise<Block>;
headerFromBytes(buff: Buffer): Promise<BlockHeader>;
headerFromStorage(header: BlockHeaderStorageData): Promise<BlockHeader>;
}

Expand Down
52 changes: 22 additions & 30 deletions packages/contracts/source/contracts/crypto/proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,54 @@ import type { Block, BlockHeader } from "./block.js";
import type { KeyPair } from "./identities.js";
import type { AggregatedSignature } from "./signatures.js";

type WithoutSignature<T> = Omit<T, "signature">;
export type MakeProposalData = WithoutSignature<SerializableProposalData>;

export interface ProposalData {
readonly blockHeader: BlockHeader;
readonly lockProof?: AggregatedSignature;
export interface ProposalDataSerializableUnsigned {
readonly round: number;
readonly data: { serialized: string };
readonly validatorIndex: number;
readonly validRound?: number;
readonly payloadSerialized: string;
readonly validatorIndex: number;
}

export interface ProposalDataSerializable extends ProposalDataSerializableUnsigned {
readonly signature: string;
}

export interface SerializableProposalData {
readonly round: number;
readonly validRound?: number;
readonly data: { serialized: string };
readonly validatorIndex: number;
readonly signature?: string;
export interface ProposalData extends ProposalDataSerializable {
readonly blockHeader: BlockHeader;
readonly lockProof?: AggregatedSignature;
}

export interface ProposedData {
export type ProposedPayloadSerializable = {
readonly block: Block;
readonly lockProof?: AggregatedSignature;
};

export interface ProposedPayload extends ProposedPayloadSerializable {
readonly serialized: string;
}

export type ProposedBlockSerializable = Omit<ProposedData, "serialized">;

export interface Proposal extends Omit<ProposalData, "data"> {
export interface Proposal extends ProposalData {
isDataDeserialized: boolean;

readonly serialized: Buffer;

deserializeData(): Promise<void>;
getData(): ProposedData;
deserializePayload(): Promise<void>;
getPayload(): ProposedPayload;

toSerializableData(): SerializableProposalData;
toSerializableData(): ProposalDataSerializable;
toData(): ProposalData;
toString(): string;
}

export interface ProposalFactory {
makeProposal(data: MakeProposalData, keyPair: KeyPair): Promise<Proposal>;
makeProposal(data: ProposalDataSerializableUnsigned, keyPair: KeyPair): Promise<Proposal>;
makeProposalFromBytes(data: Buffer): Promise<Proposal>;
makeProposalFromData(data: ProposalData): Promise<Proposal>;
makeProposedDataFromBytes(data: Buffer): Promise<ProposedData>;
}

export interface SerializeProposalOptions {
includeSignature?: boolean;
makePayloadFromBytes(data: Buffer): Promise<ProposedPayload>;
}

export interface ProposalSerializer {
serializeProposal(proposal: SerializableProposalData, options: SerializeProposalOptions): Promise<Buffer>;
serializeProposed(proposedBlock: ProposedBlockSerializable): Promise<Buffer>;
serializeProposalUnsigned(proposal: ProposalDataSerializableUnsigned): Promise<Buffer>;
serializeProposal(proposal: ProposalDataSerializable): Promise<Buffer>;
serializePayload(payload: ProposedPayloadSerializable): Promise<Buffer>;
serializeLockProof(proof: AggregatedSignature): Promise<Buffer>;
}

Expand Down
25 changes: 24 additions & 1 deletion packages/crypto-block/source/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ describe<{
factory: BlockFactory;
txFactory: Contracts.Crypto.TransactionFactory;
serializer: Serializer;
}>("Factory", ({ it, assert, beforeEach }) => {
validator: Contracts.Crypto.Validator;
}>("Factory", ({ it, assert, beforeEach, spy }) => {
const blockDataOriginal = clone(blockData);
const blockDataWithTransactionsOriginal = clone(blockDataWithTransactions);
let blockDataClone: Utils.Mutable<Contracts.Crypto.BlockData>;
Expand All @@ -45,6 +46,7 @@ describe<{
context.app.get<Contracts.Crypto.Validator>(Identifiers.Cryptography.Validator).addSchema(schema);
}

context.validator = context.app.get<Contracts.Crypto.Validator>(Identifiers.Cryptography.Validator);
context.factory = context.app.resolve(BlockFactory);
context.serializer = context.app.resolve(Serializer);
context.txFactory = context.app.get<Contracts.Crypto.TransactionFactory>(
Expand Down Expand Up @@ -167,6 +169,27 @@ describe<{
);
});

it("#headerFromBytes - should create a block header from bytes", async ({ factory, validator }) => {
const spyValidate = spy(validator, "validate");

const blockHeader = await factory.headerFromBytes(Buffer.from(serialized, "hex"));

assertBlockData(assert, blockHeader, blockData);
assert.undefined(blockHeader.serialized);
assert.undefined(blockHeader.transactions);

const blockHeaderWithTransactions = await factory.headerFromBytes(
Buffer.from(serializedWithTransactions, "hex"),
);

assertBlockData(assert, blockHeaderWithTransactions, blockDataWithTransactions);
assert.undefined(blockHeaderWithTransactions.serialized);
assert.undefined(blockHeaderWithTransactions.transactions);

spyValidate.calledTimes(2);
spyValidate.calledWith("blockHeader");
});

it("#headerFromStorage - should create a block header from storage", async ({ factory }) => {
const blockHeaderFromStorage = await factory.headerFromStorage(blockHeaderStorage);

Expand Down
23 changes: 14 additions & 9 deletions packages/crypto-block/source/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class BlockFactory implements Contracts.Crypto.BlockFactory {
): Promise<Contracts.Crypto.Block> {
const block: Contracts.Crypto.BlockHeader = { ...data, hash: await this.hashFactory.make(data) };

await this.#verify({ ...block, transactions });
await this.#verify("block", { ...block, transactions });

const serialized: Buffer = await this.serializer.serializeWithTransactions({ ...data, transactions });

Expand Down Expand Up @@ -70,6 +70,13 @@ export class BlockFactory implements Contracts.Crypto.BlockFactory {
});
}

public async headerFromBytes(serialized: Buffer): Promise<Contracts.Crypto.BlockHeader> {
const blockHeader = await this.deserializer.deserializeHeader(serialized);
await this.#verify("blockHeader", blockHeader);

return blockHeader;
}

public async headerFromStorage(
header: Contracts.Evm.BlockHeaderStorageData,
): Promise<Contracts.Crypto.BlockHeader> {
Expand Down Expand Up @@ -108,7 +115,7 @@ export class BlockFactory implements Contracts.Crypto.BlockFactory {
}

public async fromData(data: Contracts.Crypto.BlockData): Promise<Contracts.Crypto.Block> {
await this.#verify(data);
await this.#verify("block", data);

const transactions = await Promise.all(
data.transactions.map((tx) => this.transactionFactory.fromData(tx, false)),
Expand All @@ -125,21 +132,19 @@ export class BlockFactory implements Contracts.Crypto.BlockFactory {
async #fromSerialized(serialized: Buffer): Promise<Contracts.Crypto.Block> {
const deserialized = await this.deserializer.deserializeWithTransactions(serialized);

await this.#verify({ ...deserialized.data, transactions: deserialized.transactions });
await this.#verify("block", { ...deserialized.data, transactions: deserialized.transactions });

return new Block({
...deserialized,
serialized: serialized.toString("hex"),
});
}

async #verify(data: Contracts.Crypto.BlockData): Promise<void> {
const { error } = this.validator.validate("block", data);
async #verify(schema: string, data: Contracts.Crypto.BlockData | Contracts.Crypto.BlockHeader): Promise<void> {
const { error } = this.validator.validate(schema, data);

if (!error) {
return;
if (error) {
throw new BlockSchemaError(data.number, error);
}

throw new BlockSchemaError(data.number, error);
}
}
1 change: 0 additions & 1 deletion packages/crypto-messages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"@mainsail/crypto-hash-bcrypto": "workspace:*",
"@mainsail/crypto-key-pair-bls12-381": "workspace:*",
"@mainsail/crypto-key-pair-ecdsa": "workspace:*",
"@mainsail/crypto-proposal": "workspace:*",
"@mainsail/crypto-signature-bls12-381": "workspace:*",
"@mainsail/crypto-signature-ecdsa": "workspace:*",
"@mainsail/crypto-transaction": "workspace:*",
Expand Down
2 changes: 0 additions & 2 deletions packages/crypto-messages/source/schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import cryptoJson from "../../core/bin/config/devnet/core/crypto.json";
import { Application } from "@mainsail/kernel";
import { describe } from "@mainsail/test-runner";
import { prevoteData, prevoteDataNoBlock } from "../test/fixtures/index.js";
import { makeKeywords as makeProposalKeywords } from "@mainsail/crypto-proposal/source/keywords.js";
import { schemas } from "./schemas";

describe<{
Expand All @@ -27,7 +26,6 @@ describe<{

for (const keyword of Object.values({
...makeBaseKeywords(context.app.get<Configuration>(Identifiers.Cryptography.Configuration)),
...makeProposalKeywords(context.app.get<Configuration>(Identifiers.Cryptography.Configuration)),
})) {
context.validator.addKeyword(keyword);
}
Expand Down
5 changes: 0 additions & 5 deletions packages/crypto-messages/test/helpers/prepare-sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ServiceProvider as CoreCryptoConfig } from "@mainsail/crypto-config";
import { ServiceProvider as CoreCryptoHashBcrypto } from "@mainsail/crypto-hash-bcrypto";
import { ServiceProvider as CoreCryptoKeyPairBls } from "@mainsail/crypto-key-pair-bls12-381";
import { ServiceProvider as CoreCryptoKeyPairEcdsa } from "@mainsail/crypto-key-pair-ecdsa";
import { makeKeywords } from "@mainsail/crypto-proposal/source/keywords.js";
import { ServiceProvider as CoreCryptoSignatureBls } from "@mainsail/crypto-signature-bls12-381";
import { ServiceProvider as CoreCryptoSignatureEcdsa } from "@mainsail/crypto-signature-ecdsa";
import { ServiceProvider as CoreCryptoTransaction } from "@mainsail/crypto-transaction";
Expand Down Expand Up @@ -53,10 +52,6 @@ export const prepareSandbox = async (context: { app?: Application }): Promise<vo
context.app.bind(Identifiers.Cryptography.Message.Deserializer).to(Deserializer);
context.app.bind(Identifiers.Cryptography.Message.Factory).to(Factory).inSingletonScope();

for (const keyword of Object.values(makeKeywords(context.app.get(Identifiers.Cryptography.Configuration)))) {
context.app.get<Contracts.Crypto.Validator>(Identifiers.Cryptography.Validator).addKeyword(keyword);
}

for (const schema of Object.values(schemas)) {
context.app.get<Contracts.Crypto.Validator>(Identifiers.Cryptography.Validator).addSchema(schema);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/crypto-proposal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"release": "pnpm publish --access public",
"test": "pnpm run uvu source .test.ts",
"test:coverage": "c8 -r=text -r=lcov --all pnpm run test",
"test:coverage:file": "c8 -r=text -r=lcov --all pnpm run test:file",
"test:coverage:html": "c8 -r html --all pnpm run test",
"test:file": "pnpm run uvu source",
"test:fixtures": "tsx --tsconfig ../../tsconfig.test.json ./test/helpers/generate-fixtures.ts",
"uvu": "tsx --tsconfig ../../tsconfig.test.json ./node_modules/uvu/bin.js"
},
"dependencies": {
Expand Down
29 changes: 18 additions & 11 deletions packages/crypto-proposal/source/deserializer.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Application } from "@mainsail/kernel";
import { describe } from "@mainsail/test-runner";
import {
proposalData,
proposalDataWithValidRound,
serializedProposal,
serializedProposalDataWithValidRound,
Proposal,
ProposalWithValidRound,
ProposalWithLockProof,
ProposalWithLockProofAndValidRound,
lockProof,
serializedLockProof,
} from "../test/fixtures/index.js";
import { assertProposal } from "../test/helpers/asserts";
import { prepareSandbox } from "../test/helpers/prepare-sandbox";
Expand All @@ -21,14 +23,19 @@ describe<{
});

it("#deserializeProposal - should correctly deserialize", async ({ deserializer }) => {
const deserialized = await deserializer.deserializeProposal(Buffer.from(serializedProposal, "hex"));
assertProposal(assert, deserialized, proposalData);
for (const { proposalSerialized, proposalData } of [
Proposal,
ProposalWithValidRound,
ProposalWithLockProof,
ProposalWithLockProofAndValidRound,
]) {
const deserialized = await deserializer.deserializeProposal(Buffer.from(proposalSerialized, "hex"));
assertProposal(assert, deserialized, proposalData);
}
});

it("#deserializeProposal - should correctly deserialize, with validRound", async ({ deserializer }) => {
const deserialized = await deserializer.deserializeProposal(
Buffer.from(serializedProposalDataWithValidRound, "hex"),
);
assertProposal(assert, deserialized, proposalDataWithValidRound);
it("#deserializeLockProof - should correctly deserialize", async ({ deserializer }) => {
const deserialized = await deserializer.deserializeLockProof(Buffer.from(serializedLockProof, "hex"));
assert.equal(deserialized, lockProof);
});
});
5 changes: 5 additions & 0 deletions packages/crypto-proposal/source/deserializer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Identifiers } from "@mainsail/constants";
import { inject, injectable } from "@mainsail/container";
import type { Contracts } from "@mainsail/contracts";
import { InvalidProposalBytesError } from "@mainsail/exceptions";
import { ByteBuffer } from "@mainsail/utils";

import { lockProofSchema, schema } from "./serializer-schemas.js";
Expand All @@ -22,6 +23,10 @@ export class Deserializer implements Contracts.Crypto.ProposalDeserializer {
schema,
});

if (buffer.getRemainderLength() !== 0) {
throw new InvalidProposalBytesError(`Found trailing bytes of length ${buffer.getRemainderLength()}`);
}

return proposal;
}

Expand Down
Loading
Loading