Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 2 additions & 2 deletions bin/convert/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const SUPPORTED_TYPES: readonly SupportedType[] = [
name: "header",
encode: Header.Codec,
decode: Header.Codec,
json: (_spec: ChainSpec) => headerFromJson,
json: (spec: ChainSpec) => headerFromJson(spec),
process: {
options: ["as-hash"],
run(spec, data, option, blake2b) {
Expand Down Expand Up @@ -132,7 +132,7 @@ export const SUPPORTED_TYPES: readonly SupportedType[] = [
name: "stf-genesis",
encode: StateTransitionGenesis.Codec,
decode: StateTransitionGenesis.Codec,
json: () => StateTransitionGenesis.fromJson,
json: (spec: ChainSpec) => StateTransitionGenesis.fromJson(spec),
process: {
options: ["as-state", "as-jip4", "as-fuzz-message"],
run(spec: ChainSpec, data: unknown, option: string, blake2b) {
Expand Down
9 changes: 6 additions & 3 deletions bin/test-runner/w3f/runners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export const runners = [
runner("pvm", runPvmTest).fromJson(PvmTest.fromJson),
runner("gas-cost-tests", runPvmGasCostTest).fromJson(PvmGasCostTest.fromJson),
runner("reports", runReportsTest, tinyFull).fromJson(ReportsTest.fromJson),
runner("safrole", runSafroleTest, tinyFull).fromJson(SafroleTest.fromJson),
runner("safrole", runSafroleTest, tiny).fromJson(SafroleTest.fromJson(tinyChainSpec)),
runner("safrole", runSafroleTest, full).fromJson(SafroleTest.fromJson(fullChainSpec)),
runner("shuffle", runShufflingTests).fromJson(shufflingTestsFromJson),
runner("statistics/tiny", runStatisticsTestTiny, tiny).fromJson(StatisticsTestTiny.fromJson),
runner("statistics/full", runStatisticsTestFull, full).fromJson(StatisticsTestFull.fromJson),
Expand All @@ -91,12 +92,14 @@ function codecRunners(flavor: "tiny" | "full") {
runner(`codec/${flavor}/guarantees_extrinsic`, runGuaranteesExtrinsicTest, [spec]).fromJson(
guaranteesExtrinsicFromJson,
),
runner(`codec/${flavor}/header`, runHeaderTest, [spec]).fromJson(headerFromJson),
runner(`codec/${flavor}/header`, runHeaderTest, [spec]).fromJson(headerFromJson(spec)),
runner(`codec/${flavor}/preimages_extrinsic`, runPreimagesExtrinsicTest, [spec]).fromJson(
preimagesExtrinsicFromJson,
),
runner(`codec/${flavor}/refine_context`, runRefineContextTest, [spec]).fromJson(refineContextFromJson),
runner(`codec/${flavor}/tickets_extrinsic`, runTicketsExtrinsicTest, [spec]).fromJson(ticketsExtrinsicFromJson),
runner(`codec/${flavor}/tickets_extrinsic`, runTicketsExtrinsicTest, [spec]).fromJson(
ticketsExtrinsicFromJson(spec),
),
runner(`codec/${flavor}/work_item`, runWorkItemTest, [spec]).fromJson(workItemFromJson),
runner(`codec/${flavor}/work_package`, runWorkPackageTest, [spec]).fromJson(workPackageFromJson),
runner(`codec/${flavor}/work_report`, runWorkReportTest, [spec]).fromJson(workReportFromJson),
Expand Down
60 changes: 34 additions & 26 deletions bin/test-runner/w3f/safrole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,20 @@ export enum TestErrorCode {
}

class JsonState {
static fromJson: FromJson<JsonState> = {
tau: "number",
eta: json.array(fromJson.bytes32()),
lambda: json.array(validatorDataFromJson),
kappa: json.array(validatorDataFromJson),
gamma_k: json.array(validatorDataFromJson),
iota: json.array(validatorDataFromJson),
gamma_a: json.array(ticketFromJson),
gamma_s: TicketsOrKeys.fromJson,
gamma_z: json.fromString((v) => Bytes.parseBytes(v, BANDERSNATCH_RING_ROOT_BYTES).asOpaque()),
post_offenders: json.array(fromJson.bytes32()),
};
static fromJson(spec: ChainSpec): FromJson<JsonState> {
return {
tau: "number",
eta: json.array(fromJson.bytes32()),
lambda: json.array(validatorDataFromJson),
kappa: json.array(validatorDataFromJson),
gamma_k: json.array(validatorDataFromJson),
iota: json.array(validatorDataFromJson),
gamma_a: json.array(ticketFromJson(spec)),
gamma_s: TicketsOrKeys.fromJson(spec),
gamma_z: json.fromString((v) => Bytes.parseBytes(v, BANDERSNATCH_RING_ROOT_BYTES).asOpaque()),
post_offenders: json.array(fromJson.bytes32()),
};
}
// timeslot
tau!: TimeSlot;
// entropy
Expand Down Expand Up @@ -122,19 +124,23 @@ export class EpochMark {
}

export class OkOutput {
static fromJson: FromJson<OkOutput> = {
epoch_mark: json.optional(EpochMark.fromJson),
tickets_mark: json.optional<Ticket[]>(json.array(ticketFromJson)),
};
static fromJson(spec: ChainSpec): FromJson<OkOutput> {
return {
epoch_mark: json.optional(EpochMark.fromJson),
tickets_mark: json.optional<Ticket[]>(json.array(ticketFromJson(spec))),
};
}
epoch_mark?: EpochMark | null;
tickets_mark?: Ticket[] | null;
}

export class Output {
static fromJson: FromJson<Output> = {
ok: json.optional(OkOutput.fromJson),
err: json.optional("string"),
};
static fromJson(spec: ChainSpec): FromJson<Output> {
return {
ok: json.optional(OkOutput.fromJson(spec)),
err: json.optional("string"),
};
}

ok?: OkOutput;
err?: TestErrorCode;
Expand Down Expand Up @@ -196,12 +202,14 @@ class TestInput {
}

export class SafroleTest {
static fromJson: FromJson<SafroleTest> = {
input: TestInput.fromJson,
pre_state: JsonState.fromJson,
output: Output.fromJson,
post_state: JsonState.fromJson,
};
static fromJson(spec: ChainSpec): FromJson<SafroleTest> {
return {
input: TestInput.fromJson,
pre_state: JsonState.fromJson(spec),
output: Output.fromJson(spec),
post_state: JsonState.fromJson(spec),
};
}

input!: TestInput;
pre_state!: JsonState;
Expand Down
2 changes: 1 addition & 1 deletion packages/jam/block-json/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { headerFromJson } from "./header.js";
export const blockFromJson = (spec: ChainSpec) =>
json.object<Block>(
{
header: headerFromJson,
header: headerFromJson(spec),
extrinsic: getExtrinsicFromJson(spec),
},
({ header, extrinsic }) => Block.create({ header, extrinsic }),
Expand Down
11 changes: 5 additions & 6 deletions packages/jam/block-json/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type TicketAttempt, tryAsTicketAttempt } from "@typeberry/block/tickets.js";
import { Bytes, BytesBlob } from "@typeberry/bytes";
import type { ChainSpec } from "@typeberry/config";
import type { Ed25519Signature } from "@typeberry/crypto";
import { type FromJson, json } from "@typeberry/json-parser";

Expand All @@ -21,12 +22,10 @@ export namespace fromJson {

export const ed25519Signature = json.fromString<Ed25519Signature>((v) => Bytes.parseBytes(v, 64).asOpaque());

export const ticketAttempt = json.fromNumber((v) => {
if (v !== 0 && v !== 1 && v !== 2) {
throw new Error("Invalid TicketAttempt value.");
}
return tryAsTicketAttempt(v);
}) as FromJson<TicketAttempt>;
export const ticketAttempt = (spec: ChainSpec) =>
json.fromNumber((v) => {
return tryAsTicketAttempt(v, spec);
}) as FromJson<TicketAttempt>;

export const uint8Array = json.fromAny((v) => {
if (Array.isArray(v)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/jam/block-json/extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ticketsExtrinsicFromJson } from "./tickets-extrinsic.js";
export const getExtrinsicFromJson = (ctx: ChainSpec) =>
json.object<Extrinsic>(
{
tickets: ticketsExtrinsicFromJson,
tickets: ticketsExtrinsicFromJson(ctx),
preimages: preimagesExtrinsicFromJson,
guarantees: guaranteesExtrinsicFromJson,
assurances: getAssurancesExtrinsicFromJson(ctx),
Expand Down
105 changes: 54 additions & 51 deletions packages/jam/block-json/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@typeberry/block";
import { Ticket } from "@typeberry/block/tickets.js";
import { Bytes } from "@typeberry/bytes";
import type { ChainSpec } from "@typeberry/config";
import type { BandersnatchKey, Ed25519Key } from "@typeberry/crypto";
import type { BandersnatchVrfSignature } from "@typeberry/crypto/bandersnatch.js";
import { json } from "@typeberry/json-parser";
Expand Down Expand Up @@ -44,14 +45,6 @@ const epochMark = json.object<JsonEpochMarker, EpochMarker>(
(x) => EpochMarker.create({ entropy: x.entropy, ticketsEntropy: x.tickets_entropy, validators: x.validators }),
);

const ticket = json.object<Ticket>(
{
id: fromJson.bytes32(),
attempt: fromJson.ticketAttempt,
},
(x) => Ticket.create({ id: x.id, attempt: x.attempt }),
);

type JsonHeader = {
parent: HeaderHash;
parent_state_root: StateRootHash;
Expand All @@ -65,47 +58,57 @@ type JsonHeader = {
seal: BandersnatchVrfSignature;
};

export const headerFromJson = json.object<JsonHeader, Header>(
{
parent: fromJson.bytes32(),
parent_state_root: fromJson.bytes32(),
extrinsic_hash: fromJson.bytes32(),
slot: "number",
epoch_mark: json.optional(epochMark),
tickets_mark: json.optional(json.array(ticket)),
offenders_mark: json.array(fromJson.bytes32<Ed25519Key>()),
author_index: "number",
entropy_source: bandersnatchVrfSignature,
seal: bandersnatchVrfSignature,
},
({
parent,
parent_state_root,
extrinsic_hash,
slot,
epoch_mark,
tickets_mark,
offenders_mark,
author_index,
entropy_source,
seal,
}) => {
const epochMarker = epoch_mark ?? null;
const ticketsMarker =
tickets_mark === undefined || tickets_mark === null
? null
: TicketsMarker.create({ tickets: asOpaqueType(tickets_mark) });
return Header.create({
parentHeaderHash: parent,
priorStateRoot: parent_state_root,
extrinsicHash: extrinsic_hash,
timeSlotIndex: slot,
epochMarker,
ticketsMarker,
offendersMarker: offenders_mark,
bandersnatchBlockAuthorIndex: author_index,
entropySource: entropy_source,
export const headerFromJson = (spec: ChainSpec) => {
const ticket = json.object<Ticket>(
{
id: fromJson.bytes32(),
attempt: fromJson.ticketAttempt(spec),
},
(x) => Ticket.create({ id: x.id, attempt: x.attempt }),
);

return json.object<JsonHeader, Header>(
{
parent: fromJson.bytes32(),
parent_state_root: fromJson.bytes32(),
extrinsic_hash: fromJson.bytes32(),
slot: "number",
epoch_mark: json.optional(epochMark),
tickets_mark: json.optional(json.array(ticket)),
offenders_mark: json.array(fromJson.bytes32<Ed25519Key>()),
author_index: "number",
entropy_source: bandersnatchVrfSignature,
seal: bandersnatchVrfSignature,
},
({
parent,
parent_state_root,
extrinsic_hash,
slot,
epoch_mark,
tickets_mark,
offenders_mark,
author_index,
entropy_source,
seal,
});
},
);
}) => {
const epochMarker = epoch_mark ?? null;
const ticketsMarker =
tickets_mark === undefined || tickets_mark === null
? null
: TicketsMarker.create({ tickets: asOpaqueType(tickets_mark) });
return Header.create({
parentHeaderHash: parent,
priorStateRoot: parent_state_root,
extrinsicHash: extrinsic_hash,
timeSlotIndex: slot,
epochMarker,
ticketsMarker,
offendersMarker: offenders_mark,
bandersnatchBlockAuthorIndex: author_index,
entropySource: entropy_source,
seal,
});
},
);
};
19 changes: 11 additions & 8 deletions packages/jam/block-json/tickets-extrinsic.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { SignedTicket } from "@typeberry/block/tickets.js";
import { Bytes } from "@typeberry/bytes";
import type { ChainSpec } from "@typeberry/config";
import { BANDERSNATCH_PROOF_BYTES } from "@typeberry/crypto/bandersnatch.js";
import { json } from "@typeberry/json-parser";
import { fromJson } from "./common.js";

const ticketEnvelopeFromJson = json.object<SignedTicket>(
{
attempt: fromJson.ticketAttempt,
signature: json.fromString((v) => Bytes.parseBytes(v, BANDERSNATCH_PROOF_BYTES).asOpaque()),
},
(x) => SignedTicket.create({ attempt: x.attempt, signature: x.signature }),
);
export const ticketsExtrinsicFromJson = (spec: ChainSpec) => {
const ticketEnvelopeFromJson = json.object<SignedTicket>(
{
attempt: fromJson.ticketAttempt(spec),
signature: json.fromString((v) => Bytes.parseBytes(v, BANDERSNATCH_PROOF_BYTES).asOpaque()),
},
(x) => SignedTicket.create({ attempt: x.attempt, signature: x.signature }),
);

export const ticketsExtrinsicFromJson = json.array(ticketEnvelopeFromJson);
return json.array(ticketEnvelopeFromJson);
};
24 changes: 18 additions & 6 deletions packages/jam/block/tickets.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Bytes } from "@typeberry/bytes";
import { type CodecRecord, codec } from "@typeberry/codec";
import type { KnownSizeArray } from "@typeberry/collections";
import type { ChainSpec } from "@typeberry/config";
import { BANDERSNATCH_PROOF_BYTES, type BandersnatchProof } from "@typeberry/crypto/bandersnatch.js";
import { HASH_SIZE } from "@typeberry/hash";
import { tryAsU8, type U8 } from "@typeberry/numbers";
import { tryAsU8, tryAsU32, type U8 } from "@typeberry/numbers";
import { asOpaqueType, type Opaque, WithDebug } from "@typeberry/utils";
import { codecKnownSizeArray, codecWithContext } from "./codec-utils.js";

Expand All @@ -14,15 +15,27 @@ import { codecKnownSizeArray, codecWithContext } from "./codec-utils.js";
* https://graypaper.fluffylabs.dev/#/579bd12/417200417400
*/
export type TicketAttempt = Opaque<U8, "TicketAttempt[0|1|2]">;
export function tryAsTicketAttempt(x: number): TicketAttempt {
export function tryAsTicketAttempt(x: number, chainSpec: ChainSpec): TicketAttempt {
if (x >= chainSpec.ticketsPerValidator) {
throw new Error(`Ticket attempt ${x} is out of bounds [0, ${chainSpec.ticketsPerValidator})`);
}
return asOpaqueType(tryAsU8(x));
}

const ticketAttemptCodec = codecWithContext((context) => {
return codec.varU32.convert<TicketAttempt>(
(x) => {
tryAsTicketAttempt(x, context);
return tryAsU32(x);
},
(x) => tryAsTicketAttempt(x, context),
);
});

/* Bandersnatch-signed ticket contest entry. */
export class SignedTicket extends WithDebug {
static Codec = codec.Class(SignedTicket, {
// TODO [ToDr] we should verify that attempt is either 0|1|2.
attempt: codec.u8.asOpaque<TicketAttempt>(),
attempt: ticketAttemptCodec,
signature: codec.bytes(BANDERSNATCH_PROOF_BYTES).asOpaque<BandersnatchProof>(),
});

Expand All @@ -44,8 +57,7 @@ export class SignedTicket extends WithDebug {
export class Ticket extends WithDebug {
static Codec = codec.Class(Ticket, {
id: codec.bytes(HASH_SIZE),
// TODO [ToDr] we should verify that attempt is either 0|1|2.
attempt: codec.u8.asOpaque<TicketAttempt>(),
attempt: ticketAttemptCodec,
});

static create({ id, attempt }: CodecRecord<Ticket>) {
Expand Down
Loading
Loading