diff --git a/package-lock.json b/package-lock.json index 9cb026935..a9bdb5d9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7271,9 +7271,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/packages/jam/in-core/externalities/index.ts b/packages/jam/in-core/externalities/index.ts new file mode 100644 index 000000000..92c87dcdc --- /dev/null +++ b/packages/jam/in-core/externalities/index.ts @@ -0,0 +1,3 @@ +export * from "./is-authorized-fetch.js"; +export * from "./refine.js"; +export * from "./refine-fetch.js"; diff --git a/packages/jam/in-core/externalities/is-authorized-fetch.test.ts b/packages/jam/in-core/externalities/is-authorized-fetch.test.ts new file mode 100644 index 000000000..e5b9c4e83 --- /dev/null +++ b/packages/jam/in-core/externalities/is-authorized-fetch.test.ts @@ -0,0 +1,115 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; + +import type { CodeHash } from "@typeberry/block"; +import { tryAsServiceGas, tryAsServiceId, tryAsTimeSlot } from "@typeberry/block"; +import { RefineContext } from "@typeberry/block/refine-context.js"; +import { WorkItem } from "@typeberry/block/work-item.js"; +import { tryAsWorkItemsCount, WorkPackage } from "@typeberry/block/work-package.js"; +import { Bytes, BytesBlob } from "@typeberry/bytes"; +import { Encoder } from "@typeberry/codec"; +import { asKnownSize, FixedSizeArray } from "@typeberry/collections"; +import { fullChainSpec, tinyChainSpec } from "@typeberry/config"; +import { HASH_SIZE } from "@typeberry/hash"; +import { tryAsU16, tryAsU64 } from "@typeberry/numbers"; +import { buildWorkPackageFetchData } from "@typeberry/transition/externalities/fetch-externalities.js"; +import { IsAuthorizedFetchExternalities } from "./is-authorized-fetch.js"; + +function fetchDataFor(pkg: WorkPackage, chainSpec = tinyChainSpec) { + return buildWorkPackageFetchData(chainSpec, pkg); +} + +function buildWorkItem(overrides: { service?: number; payloadLen?: number } = {}) { + return WorkItem.create({ + service: tryAsServiceId(overrides.service ?? 1), + codeHash: Bytes.fill(HASH_SIZE, 7).asOpaque(), + payload: BytesBlob.blobFrom(new Uint8Array(overrides.payloadLen ?? 3).fill(0xab)), + refineGasLimit: tryAsServiceGas(1_000_000), + accumulateGasLimit: tryAsServiceGas(2_000_000), + importSegments: asKnownSize([]), + extrinsic: [], + exportCount: tryAsU16(0), + }); +} + +function buildPackage(items: WorkItem[] = [buildWorkItem({})]) { + return WorkPackage.create({ + authToken: BytesBlob.blobFrom(new Uint8Array([1, 2, 3])), + authCodeHost: tryAsServiceId(42), + authCodeHash: Bytes.fill(HASH_SIZE, 9).asOpaque(), + authConfiguration: BytesBlob.blobFrom(new Uint8Array([4, 5, 6, 7])), + context: RefineContext.create({ + anchor: Bytes.fill(HASH_SIZE, 1).asOpaque(), + stateRoot: Bytes.fill(HASH_SIZE, 2).asOpaque(), + beefyRoot: Bytes.fill(HASH_SIZE, 3).asOpaque(), + lookupAnchor: Bytes.fill(HASH_SIZE, 4).asOpaque(), + lookupAnchorSlot: tryAsTimeSlot(16), + prerequisites: [], + }), + items: FixedSizeArray.new(items, tryAsWorkItemsCount(items.length)), + }); +} + +describe("IsAuthorizedFetchExternalities", () => { + it("returns different constants for different chain specs", () => { + const tinyExt = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage(), tinyChainSpec)); + const fullExt = new IsAuthorizedFetchExternalities(fullChainSpec, fetchDataFor(buildPackage(), fullChainSpec)); + assert.notStrictEqual(tinyExt.constants().length, 0); + assert.notDeepStrictEqual(tinyExt.constants(), fullExt.constants()); + }); + + it("returns encoded work package", () => { + const pkg = buildPackage(); + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(pkg)); + const expected = Encoder.encodeObject(WorkPackage.Codec, pkg, tinyChainSpec); + assert.deepStrictEqual(ext.workPackage().raw, expected.raw); + }); + + it("returns auth configuration and auth token from the package", () => { + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage())); + assert.deepStrictEqual(ext.authConfiguration().raw, new Uint8Array([4, 5, 6, 7])); + assert.deepStrictEqual(ext.authToken().raw, new Uint8Array([1, 2, 3])); + }); + + it("returns encoded refine context", () => { + const pkg = buildPackage(); + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(pkg)); + const expected = Encoder.encodeObject(RefineContext.Codec, pkg.context); + assert.deepStrictEqual(ext.refineContext().raw, expected.raw); + }); + + it("returns concatenated work item summaries with 62 bytes per item", () => { + const items = [buildWorkItem({ service: 1 }), buildWorkItem({ service: 2, payloadLen: 5 })]; + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage(items))); + assert.strictEqual(ext.allWorkItems().length, 62 * items.length); + }); + + it("returns a single work item summary (kind 12)", () => { + const items = [buildWorkItem({ service: 1 }), buildWorkItem({ service: 2, payloadLen: 10 })]; + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage(items))); + const one = ext.oneWorkItem(tryAsU64(1)); + assert.ok(one !== null); + assert.strictEqual(one.length, 62); + const serviceId = new DataView(one.raw.buffer, one.raw.byteOffset, 4).getUint32(0, true); + assert.strictEqual(serviceId, 2); + }); + + it("returns null for one work item when index is out of range", () => { + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage())); + assert.strictEqual(ext.oneWorkItem(tryAsU64(99)), null); + }); + + it("returns the raw payload of a work item (kind 13)", () => { + const items = [buildWorkItem({ service: 1, payloadLen: 2 }), buildWorkItem({ service: 2, payloadLen: 5 })]; + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage(items))); + const payload = ext.workItemPayload(tryAsU64(1)); + assert.ok(payload !== null); + assert.strictEqual(payload.length, 5); + assert.ok(payload.raw.every((x: number) => x === 0xab)); + }); + + it("returns null for payload when index is out of range", () => { + const ext = new IsAuthorizedFetchExternalities(tinyChainSpec, fetchDataFor(buildPackage())); + assert.strictEqual(ext.workItemPayload(tryAsU64(99)), null); + }); +}); diff --git a/packages/jam/in-core/externalities/is-authorized-fetch.ts b/packages/jam/in-core/externalities/is-authorized-fetch.ts new file mode 100644 index 000000000..3413c1173 --- /dev/null +++ b/packages/jam/in-core/externalities/is-authorized-fetch.ts @@ -0,0 +1,56 @@ +import type { BytesBlob } from "@typeberry/bytes"; +import type { ChainSpec } from "@typeberry/config"; +import { general } from "@typeberry/jam-host-calls"; +import type { U64 } from "@typeberry/numbers"; +import { + getEncodedConstants, + u64ToArrayIndex, + type WorkPackageFetchData, +} from "@typeberry/transition/externalities/fetch-externalities.js"; + +export class IsAuthorizedFetchExternalities implements general.IIsAuthorizedFetch { + readonly context = general.FetchContext.IsAuthorized; + + constructor( + private readonly chainSpec: ChainSpec, + private readonly pkg: WorkPackageFetchData, + ) {} + + constants(): BytesBlob { + return getEncodedConstants(this.chainSpec); + } + + workPackage(): BytesBlob { + return this.pkg.packageView.encoded(); + } + + authConfiguration(): BytesBlob { + return this.pkg.packageView.authConfiguration.view(); + } + + authToken(): BytesBlob { + return this.pkg.packageView.authToken.view(); + } + + refineContext(): BytesBlob { + return this.pkg.packageView.context.encoded(); + } + + allWorkItems(): BytesBlob { + return this.pkg.workItemSummaries.encoded(); + } + + oneWorkItem(workItem: U64): BytesBlob | null { + const idx = u64ToArrayIndex(workItem, this.pkg.workItemSummaries.length); + return idx === null ? null : (this.pkg.workItemSummaries.get(idx)?.encoded() ?? null); + } + + workItemPayload(workItem: U64): BytesBlob | null { + const items = this.pkg.packageView.items.view(); + const idx = u64ToArrayIndex(workItem, items.length); + if (idx === null) { + return null; + } + return items.get(idx)?.view().payload.view() ?? null; + } +} diff --git a/packages/jam/in-core/externalities/refine-fetch.test.ts b/packages/jam/in-core/externalities/refine-fetch.test.ts new file mode 100644 index 000000000..1a0ca8dcc --- /dev/null +++ b/packages/jam/in-core/externalities/refine-fetch.test.ts @@ -0,0 +1,270 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; + +import type { CodeHash } from "@typeberry/block"; +import { tryAsServiceGas, tryAsServiceId, tryAsTimeSlot } from "@typeberry/block"; +import { RefineContext } from "@typeberry/block/refine-context.js"; +import { ImportSpec, WorkItem, type WorkItemExtrinsic, WorkItemExtrinsicSpec } from "@typeberry/block/work-item.js"; +import { SEGMENT_BYTES, tryAsSegmentIndex } from "@typeberry/block/work-item-segment.js"; +import { tryAsWorkItemsCount, WorkPackage } from "@typeberry/block/work-package.js"; +import { Bytes, BytesBlob } from "@typeberry/bytes"; +import { Encoder } from "@typeberry/codec"; +import { asKnownSize, FixedSizeArray } from "@typeberry/collections"; +import { type ChainSpec, fullChainSpec, tinyChainSpec } from "@typeberry/config"; +import { HASH_SIZE } from "@typeberry/hash"; +import { tryAsU16, tryAsU32, tryAsU64 } from "@typeberry/numbers"; +import { buildWorkPackageFetchData } from "@typeberry/transition/externalities/fetch-externalities.js"; +import { asOpaqueType } from "@typeberry/utils"; +import type { ImportedSegment, PerWorkItem } from "./refine-fetch.js"; +import { RefineFetchExternalities } from "./refine-fetch.js"; + +const asExtrinsic = (bytes: BytesBlob): WorkItemExtrinsic => asOpaqueType(bytes); + +function buildWorkItem(overrides: { + service?: number; + payloadLen?: number; + exportCount?: number; + importCount?: number; + extrinsicCount?: number; +}) { + const codeHash = Bytes.fill(HASH_SIZE, 7).asOpaque(); + const imports = Array.from({ length: overrides.importCount ?? 0 }, (_, i) => + ImportSpec.create({ treeRoot: Bytes.zero(HASH_SIZE), index: tryAsSegmentIndex(i) }), + ); + const extrinsicSpecs = Array.from({ length: overrides.extrinsicCount ?? 0 }, () => + WorkItemExtrinsicSpec.create({ hash: Bytes.zero(HASH_SIZE).asOpaque(), len: tryAsU32(0) }), + ); + return WorkItem.create({ + service: tryAsServiceId(overrides.service ?? 1), + codeHash, + payload: BytesBlob.blobFrom(new Uint8Array(overrides.payloadLen ?? 3).fill(0xab)), + refineGasLimit: tryAsServiceGas(1_000_000), + accumulateGasLimit: tryAsServiceGas(2_000_000), + importSegments: asKnownSize(imports), + extrinsic: extrinsicSpecs, + exportCount: tryAsU16(overrides.exportCount ?? 0), + }); +} + +function buildWorkPackage(items: WorkItem[]) { + return WorkPackage.create({ + authToken: BytesBlob.blobFrom(new Uint8Array([1, 2, 3])), + authCodeHost: tryAsServiceId(42), + authCodeHash: Bytes.fill(HASH_SIZE, 9).asOpaque(), + authConfiguration: BytesBlob.blobFrom(new Uint8Array([4, 5, 6, 7])), + context: RefineContext.create({ + anchor: Bytes.fill(HASH_SIZE, 1).asOpaque(), + stateRoot: Bytes.fill(HASH_SIZE, 2).asOpaque(), + beefyRoot: Bytes.fill(HASH_SIZE, 3).asOpaque(), + lookupAnchor: Bytes.fill(HASH_SIZE, 4).asOpaque(), + lookupAnchorSlot: tryAsTimeSlot(16), + prerequisites: [], + }), + items: FixedSizeArray.new(items, tryAsWorkItemsCount(items.length)), + }); +} + +function prepareRefineData( + opts: { + chainSpec?: ChainSpec; + items?: WorkItem[]; + currentWorkItemIndex?: number; + extrinsics?: PerWorkItem; + imports?: PerWorkItem; + authorizerTrace?: BytesBlob; + } = {}, +) { + const chainSpec = opts.chainSpec ?? tinyChainSpec; + const items = opts.items ?? [buildWorkItem({})]; + const workPackage = buildWorkPackage(items); + const packageData = buildWorkPackageFetchData(chainSpec, workPackage); + return new RefineFetchExternalities(chainSpec, { + packageData, + currentWorkItemIndex: opts.currentWorkItemIndex ?? 0, + imports: opts.imports ?? asKnownSize(items.map(() => [])), + extrinsics: opts.extrinsics ?? asKnownSize(items.map(() => [])), + authorizerTrace: opts.authorizerTrace ?? BytesBlob.empty(), + }); +} + +describe("RefineFetchExternalities", () => { + it("should return different constants for different chain specs", () => { + const tinyExt = prepareRefineData({ chainSpec: tinyChainSpec }); + const fullExt = prepareRefineData({ chainSpec: fullChainSpec }); + + assert.notStrictEqual(tinyExt.constants().length, 0); + assert.notStrictEqual(fullExt.constants().length, 0); + assert.notDeepStrictEqual(tinyExt.constants(), fullExt.constants()); + }); + + it("should return entropy H_0 (zero hash) per GP §B.3", () => { + const ext = prepareRefineData(); + const entropy = ext.entropy(); + assert.strictEqual(entropy.length, HASH_SIZE); + assert.ok(entropy.isEqualTo(Bytes.zero(HASH_SIZE).asOpaque())); + }); + + it("should return the supplied authorizer trace", () => { + const trace = BytesBlob.blobFrom(new Uint8Array([0xaa, 0xbb, 0xcc])); + const ext = prepareRefineData({ authorizerTrace: trace }); + assert.deepStrictEqual(ext.authorizerTrace().raw, trace.raw); + }); + + it("should return an extrinsic by work item index and extrinsic index", () => { + const items = [buildWorkItem({}), buildWorkItem({ service: 2 })]; + const extrinsics: PerWorkItem = asKnownSize([ + [asExtrinsic(BytesBlob.blobFrom(new Uint8Array([1])))], + [ + asExtrinsic(BytesBlob.blobFrom(new Uint8Array([2, 2]))), + asExtrinsic(BytesBlob.blobFrom(new Uint8Array([3, 3, 3]))), + ], + ]); + const ext = prepareRefineData({ items, extrinsics }); + + const other = ext.workItemExtrinsic(tryAsU64(1), tryAsU64(1)); + assert.ok(other !== null); + assert.deepStrictEqual(other.raw, new Uint8Array([3, 3, 3])); + }); + + it("should return current item's extrinsic when workItem is null", () => { + const items = [buildWorkItem({}), buildWorkItem({ service: 2 })]; + const extrinsics: PerWorkItem = asKnownSize([ + [asExtrinsic(BytesBlob.blobFrom(new Uint8Array([9])))], + [asExtrinsic(BytesBlob.blobFrom(new Uint8Array([8])))], + ]); + const ext = prepareRefineData({ items, extrinsics, currentWorkItemIndex: 1 }); + + const mine = ext.workItemExtrinsic(null, tryAsU64(0)); + assert.ok(mine !== null); + assert.deepStrictEqual(mine.raw, new Uint8Array([8])); + }); + + it("should return null for out-of-range extrinsic indices", () => { + const items = [buildWorkItem({})]; + const extrinsics: PerWorkItem = asKnownSize([ + [asExtrinsic(BytesBlob.blobFrom(new Uint8Array([1])))], + ]); + const ext = prepareRefineData({ items, extrinsics }); + + assert.strictEqual(ext.workItemExtrinsic(tryAsU64(5), tryAsU64(0)), null); + assert.strictEqual(ext.workItemExtrinsic(tryAsU64(0), tryAsU64(5)), null); + assert.strictEqual(ext.workItemExtrinsic(null, tryAsU64(5)), null); + }); + + it("should treat U64 indices above the safe-integer range as out of range", () => { + const items = [buildWorkItem({})]; + const ext = prepareRefineData({ items }); + const huge = tryAsU64(2n ** 53n); // first value > Number.MAX_SAFE_INTEGER + assert.strictEqual(ext.workItemExtrinsic(huge, tryAsU64(0)), null); + assert.strictEqual(ext.workItemExtrinsic(null, huge), null); + assert.strictEqual(ext.workItemImport(huge, tryAsU64(0)), null); + assert.strictEqual(ext.oneWorkItem(huge), null); + assert.strictEqual(ext.workItemPayload(huge), null); + }); + + it("should return an import segment by work item index and segment index", () => { + const items = [buildWorkItem({}), buildWorkItem({ service: 2 })]; + const segBytes = new Uint8Array(SEGMENT_BYTES).fill(0x55); + const imports: PerWorkItem = asKnownSize([ + [], + [{ index: tryAsSegmentIndex(0), data: Bytes.fromBlob(segBytes, SEGMENT_BYTES) }], + ]); + const ext = prepareRefineData({ items, imports }); + + const imp = ext.workItemImport(tryAsU64(1), tryAsU64(0)); + assert.ok(imp !== null); + assert.deepStrictEqual(imp.raw, segBytes); + }); + + it("should return null for out-of-range import indices", () => { + const ext = prepareRefineData(); + assert.strictEqual(ext.workItemImport(tryAsU64(10), tryAsU64(0)), null); + assert.strictEqual(ext.workItemImport(null, tryAsU64(10)), null); + assert.strictEqual(ext.workItemImport(tryAsU64(0), tryAsU64(10)), null); + }); + + it("should return encoded work package", () => { + const items = [buildWorkItem({})]; + const ext = prepareRefineData({ items }); + const expected = Encoder.encodeObject(WorkPackage.Codec, buildWorkPackage(items), tinyChainSpec); + assert.deepStrictEqual(ext.workPackage().raw, expected.raw); + }); + + it("should return auth configuration and auth token from the package", () => { + const ext = prepareRefineData(); + assert.deepStrictEqual(ext.authConfiguration().raw, new Uint8Array([4, 5, 6, 7])); + assert.deepStrictEqual(ext.authToken().raw, new Uint8Array([1, 2, 3])); + }); + + it("should return encoded refine context", () => { + const ext = prepareRefineData(); + const context = RefineContext.create({ + anchor: Bytes.fill(HASH_SIZE, 1).asOpaque(), + stateRoot: Bytes.fill(HASH_SIZE, 2).asOpaque(), + beefyRoot: Bytes.fill(HASH_SIZE, 3).asOpaque(), + lookupAnchor: Bytes.fill(HASH_SIZE, 4).asOpaque(), + lookupAnchorSlot: tryAsTimeSlot(16), + prerequisites: [], + }); + const expected = Encoder.encodeObject(RefineContext.Codec, context); + assert.deepStrictEqual(ext.refineContext().raw, expected.raw); + }); + + it("should return concatenated work item summaries (kind 11) with 62 bytes per item", () => { + const items = [ + buildWorkItem({ service: 1, payloadLen: 7, exportCount: 2, importCount: 1, extrinsicCount: 0 }), + buildWorkItem({ service: 2, payloadLen: 4, exportCount: 0, importCount: 0, extrinsicCount: 3 }), + ]; + const ext = prepareRefineData({ items }); + const all = ext.allWorkItems(); + assert.strictEqual(all.length, 62 * items.length); + }); + + it("should return a single work item summary (kind 12)", () => { + const items = [buildWorkItem({ service: 1 }), buildWorkItem({ service: 2, payloadLen: 10 })]; + const ext = prepareRefineData({ items }); + + const one = ext.oneWorkItem(tryAsU64(1)); + assert.ok(one !== null); + assert.strictEqual(one.length, 62); + + // first 4 bytes are the service id (u32 LE). + const serviceId = new DataView(one.raw.buffer, one.raw.byteOffset, 4).getUint32(0, true); + assert.strictEqual(serviceId, 2); + // payload length is the last 4 bytes (u32 LE). + const payloadLen = new DataView(one.raw.buffer, one.raw.byteOffset + 58, 4).getUint32(0, true); + assert.strictEqual(payloadLen, 10); + }); + + it("should return null for one work item when index is out of range", () => { + const ext = prepareRefineData(); + assert.strictEqual(ext.oneWorkItem(tryAsU64(99)), null); + }); + + it("should return the raw payload of a work item (kind 13)", () => { + const items = [buildWorkItem({ service: 1, payloadLen: 2 }), buildWorkItem({ service: 2, payloadLen: 5 })]; + const ext = prepareRefineData({ items }); + const payload = ext.workItemPayload(tryAsU64(1)); + assert.ok(payload !== null); + assert.strictEqual(payload.length, 5); + assert.ok(payload.raw.every((x: number) => x === 0xab)); + }); + + it("should return null for payload when index is out of range", () => { + const ext = prepareRefineData(); + assert.strictEqual(ext.workItemPayload(tryAsU64(99)), null); + }); + + // guard against silent accidental changes to the helpers — tryAsU32 ensures + // encoded lengths match GP's S(w) spec. + it("uses unsigned little-endian u32 for payload length regardless of platform", () => { + const items = [buildWorkItem({ service: 1, payloadLen: 0x1234 })]; + const ext = prepareRefineData({ items }); + const one = ext.oneWorkItem(tryAsU64(0)); + assert.ok(one !== null); + const payloadLen = new DataView(one.raw.buffer, one.raw.byteOffset + 58, 4).getUint32(0, true); + assert.strictEqual(payloadLen, 0x1234); + // tryAsU32 would throw on negative values + assert.doesNotThrow(() => tryAsU32(0x1234)); + }); +}); diff --git a/packages/jam/in-core/externalities/refine-fetch.ts b/packages/jam/in-core/externalities/refine-fetch.ts new file mode 100644 index 000000000..a14fafdb2 --- /dev/null +++ b/packages/jam/in-core/externalities/refine-fetch.ts @@ -0,0 +1,125 @@ +import type { EntropyHash, Segment, SegmentIndex } from "@typeberry/block"; +import type { WorkItemExtrinsic } from "@typeberry/block/work-item.js"; +import { Bytes, type BytesBlob } from "@typeberry/bytes"; +import type { KnownSizeArray } from "@typeberry/collections"; +import type { ChainSpec } from "@typeberry/config"; +import { HASH_SIZE } from "@typeberry/hash"; +import { general } from "@typeberry/jam-host-calls"; +import type { U64 } from "@typeberry/numbers"; +import { + getEncodedConstants, + u64ToArrayIndex, + type WorkPackageFetchData, +} from "@typeberry/transition/externalities/fetch-externalities.js"; + +/** A single decoded import segment passed into refine. */ +export type ImportedSegment = { + index: SegmentIndex; + data: Segment; +}; + +/** An array whose length matches the work-package's work-item count. */ +export type PerWorkItem = KnownSizeArray; + +export type RefineFetchData = { + /** Pre-computed per-work-package encodings. */ + packageData: WorkPackageFetchData; + /** Index of the work item currently being refined (`i` in GP). */ + currentWorkItemIndex: number; + /** Imports per work item (`ī`). */ + imports: PerWorkItem; + /** Extrinsics per work item (`x̄`). */ + extrinsics: PerWorkItem; + /** Authorizer trace produced by Is-Authorized (`r`). */ + authorizerTrace: BytesBlob; +}; + +export class RefineFetchExternalities implements general.IRefineFetch { + readonly context = general.FetchContext.Refine; + + constructor( + private readonly chainSpec: ChainSpec, + private readonly data: RefineFetchData, + ) {} + + constants(): BytesBlob { + return getEncodedConstants(this.chainSpec); + } + + /** + * Refine entropy is `H₀` (zero hash) per GP §B.3. + * + * https://graypaper.fluffylabs.dev/#/ab2cdbd/2fe0012fe201?v=0.7.2 + */ + entropy(): EntropyHash { + return Bytes.zero(HASH_SIZE).asOpaque(); + } + + authorizerTrace(): BytesBlob { + return this.data.authorizerTrace; + } + + workItemExtrinsic(workItem: U64 | null, index: U64): BytesBlob | null { + const itemIdx = + workItem === null ? this.data.currentWorkItemIndex : u64ToArrayIndex(workItem, this.data.extrinsics.length); + if (itemIdx === null) { + return null; + } + const perItem = this.data.extrinsics[itemIdx]; + const xIdx = u64ToArrayIndex(index, perItem.length); + if (xIdx === null) { + return null; + } + return perItem[xIdx]; + } + + workItemImport(workItem: U64 | null, index: U64): BytesBlob | null { + const itemIdx = + workItem === null ? this.data.currentWorkItemIndex : u64ToArrayIndex(workItem, this.data.imports.length); + if (itemIdx === null) { + return null; + } + const perItem = this.data.imports[itemIdx]; + const segIdx = u64ToArrayIndex(index, perItem.length); + if (segIdx === null) { + return null; + } + // `Segment` extends `BytesBlob`, so it can be returned directly. + return perItem[segIdx].data; + } + + workPackage(): BytesBlob { + return this.data.packageData.packageView.encoded(); + } + + authConfiguration(): BytesBlob { + return this.data.packageData.packageView.authConfiguration.view(); + } + + authToken(): BytesBlob { + return this.data.packageData.packageView.authToken.view(); + } + + refineContext(): BytesBlob { + return this.data.packageData.packageView.context.encoded(); + } + + allWorkItems(): BytesBlob { + return this.data.packageData.workItemSummaries.encoded(); + } + + oneWorkItem(workItem: U64): BytesBlob | null { + const summaries = this.data.packageData.workItemSummaries; + const idx = u64ToArrayIndex(workItem, summaries.length); + return idx === null ? null : (summaries.get(idx)?.encoded() ?? null); + } + + workItemPayload(workItem: U64): BytesBlob | null { + const items = this.data.packageData.packageView.items.view(); + const idx = u64ToArrayIndex(workItem, items.length); + if (idx === null) { + return null; + } + return items.get(idx)?.view().payload.view() ?? null; + } +} diff --git a/packages/jam/in-core/in-core.ts b/packages/jam/in-core/in-core.ts index ab4625e7a..bef12a6d9 100644 --- a/packages/jam/in-core/in-core.ts +++ b/packages/jam/in-core/in-core.ts @@ -11,11 +11,13 @@ import type { Blake2b, WithHash } from "@typeberry/hash"; import { HASH_SIZE } from "@typeberry/hash"; import { Logger } from "@typeberry/logger"; import { tryAsU8, tryAsU16, tryAsU32 } from "@typeberry/numbers"; +import { buildWorkPackageFetchData } from "@typeberry/transition/externalities/fetch-externalities.js"; import { assertEmpty, Result } from "@typeberry/utils"; +import type { ImportedSegment, PerWorkItem } from "./externalities/index.js"; import { AuthorizationError, type AuthorizationOk, IsAuthorized } from "./is-authorized.js"; -import { type ImportedSegment, type PerWorkItem, Refine, type RefineItemResult } from "./refine.js"; +export type { ImportedSegment, PerWorkItem }; -export type { ImportedSegment, PerWorkItem, RefineItemResult } from "./refine.js"; +import { Refine, type RefineItemResult } from "./refine.js"; export type RefineResult = { report: WorkReport; @@ -70,9 +72,7 @@ export class InCore { extrinsics: PerWorkItem, ): Promise> { const workPackageHash = workPackageAndHash.hash; - const { context, authToken, authCodeHash, authCodeHost, authConfiguration, items, ...rest } = - workPackageAndHash.data; - assertEmpty(rest); + const { context, items } = workPackageAndHash.data; // TODO [ToDr] Verify BEEFY root // TODO [ToDr] Verify prerequisites @@ -111,15 +111,11 @@ export class InCore { ); } + // Eagerly build the per-package fetch data so we pay the encoding cost + const packageFetchData = buildWorkPackageFetchData(this.chainSpec, workPackageAndHash.data); + // Check authorization - const authResult = await this.isAuthorized.invoke( - state, - core, - authToken, - authCodeHost, - authCodeHash, - authConfiguration, - ); + const authResult = await this.isAuthorized.invoke(state, core, packageFetchData); if (authResult.isError) { return Result.error( RefineError.AuthorizationError, @@ -138,6 +134,7 @@ export class InCore { const result = await this.refineItem.invoke( state, lookupState, + packageFetchData, idx, item, imports, @@ -145,6 +142,7 @@ export class InCore { core, workPackageHash, exportOffset, + authResult.ok.authorizationOutput, ); refineResults.push(result); exportOffset += result.exports.length; diff --git a/packages/jam/in-core/is-authorized.test.ts b/packages/jam/in-core/is-authorized.test.ts index e419f0643..f125d8948 100644 --- a/packages/jam/in-core/is-authorized.test.ts +++ b/packages/jam/in-core/is-authorized.test.ts @@ -4,14 +4,53 @@ import { resolve } from "node:path"; import { before, describe, it } from "node:test"; import type { CodeHash } from "@typeberry/block"; import { tryAsCoreIndex, tryAsServiceGas, tryAsServiceId, tryAsTimeSlot } from "@typeberry/block"; +import { RefineContext } from "@typeberry/block/refine-context.js"; +import { WorkItem } from "@typeberry/block/work-item.js"; +import { tryAsWorkItemsCount, WorkPackage } from "@typeberry/block/work-package.js"; import { Bytes, BytesBlob } from "@typeberry/bytes"; -import { HashDictionary } from "@typeberry/collections"; +import { asKnownSize, FixedSizeArray, HashDictionary } from "@typeberry/collections"; import { PvmBackend, tinyChainSpec } from "@typeberry/config"; import { Blake2b, HASH_SIZE, type OpaqueHash } from "@typeberry/hash"; -import { tryAsU32, tryAsU64 } from "@typeberry/numbers"; +import { tryAsU16, tryAsU32, tryAsU64 } from "@typeberry/numbers"; import { InMemoryService, InMemoryState, PreimageItem, ServiceAccountInfo } from "@typeberry/state"; +import { buildWorkPackageFetchData } from "@typeberry/transition/externalities/fetch-externalities.js"; import { AuthorizationError, IsAuthorized } from "./is-authorized.js"; +function buildPackageAndFetchData(authCodeHash: CodeHash, authToken: BytesBlob, authConfiguration: BytesBlob) { + const pkg = buildPackage(authCodeHash, authToken, authConfiguration); + return { pkg, fetchData: buildWorkPackageFetchData(tinyChainSpec, pkg) }; +} + +function buildPackage(authCodeHash: CodeHash, authToken: BytesBlob, authConfiguration: BytesBlob): WorkPackage { + const items = [ + WorkItem.create({ + service: tryAsServiceId(1), + codeHash: Bytes.zero(HASH_SIZE).asOpaque(), + refineGasLimit: tryAsServiceGas(1_000_000), + accumulateGasLimit: tryAsServiceGas(1_000_000), + exportCount: tryAsU16(0), + payload: BytesBlob.empty(), + importSegments: asKnownSize([]), + extrinsic: [], + }), + ]; + return WorkPackage.create({ + authToken, + authCodeHost: AUTH_SERVICE_ID, + authCodeHash, + authConfiguration, + context: RefineContext.create({ + anchor: Bytes.zero(HASH_SIZE).asOpaque(), + stateRoot: Bytes.zero(HASH_SIZE).asOpaque(), + beefyRoot: Bytes.zero(HASH_SIZE).asOpaque(), + lookupAnchor: Bytes.zero(HASH_SIZE).asOpaque(), + lookupAnchorSlot: tryAsTimeSlot(16), + prerequisites: [], + }), + items: FixedSizeArray.new(items, tryAsWorkItemsCount(1)), + }); +} + let blake2b: Blake2b; before(async () => { @@ -67,7 +106,8 @@ describe("IsAuthorized", () => { const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b); const token = BytesBlob.blobFromString("hello"); - const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), token, AUTH_SERVICE_ID, authCodeHash, token); + const { fetchData } = buildPackageAndFetchData(authCodeHash, token, token); + const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), fetchData); assert.strictEqual(result.isOk, true, `Expected OK but got error: ${result.isError ? result.details() : ""}`); @@ -91,14 +131,8 @@ describe("IsAuthorized", () => { const state = createStateWithService(authCodeHash, AUTHORIZER_PVM); const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b); - const result = await isAuthorized.invoke( - state, - tryAsCoreIndex(0), - BytesBlob.empty(), - AUTH_SERVICE_ID, - authCodeHash, - BytesBlob.empty(), - ); + const empty = buildPackageAndFetchData(authCodeHash, BytesBlob.empty(), BytesBlob.empty()); + const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), empty.fetchData); assert.strictEqual(result.isOk, true, `Expected OK but got error: ${result.isError ? result.details() : ""}`); const outputStr = Buffer.from(result.ok.authorizationOutput.raw).toString("utf8"); @@ -110,14 +144,12 @@ describe("IsAuthorized", () => { const state = createStateWithService(authCodeHash, AUTHORIZER_PVM); const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b); - const result = await isAuthorized.invoke( - state, - tryAsCoreIndex(0), - BytesBlob.blobFromString("wrong"), - AUTH_SERVICE_ID, + const mismatch = buildPackageAndFetchData( authCodeHash, + BytesBlob.blobFromString("wrong"), BytesBlob.blobFromString("right"), ); + const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), mismatch.fetchData); assert.strictEqual(result.isError, true); assert.strictEqual(result.error, AuthorizationError.PvmFailed); @@ -131,14 +163,8 @@ describe("IsAuthorized", () => { }); const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b); - const result = await isAuthorized.invoke( - state, - tryAsCoreIndex(0), - BytesBlob.empty(), - AUTH_SERVICE_ID, - authCodeHash, - BytesBlob.empty(), - ); + const missing = buildPackageAndFetchData(authCodeHash, BytesBlob.empty(), BytesBlob.empty()); + const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), missing.fetchData); assert.strictEqual(result.isError, true); assert.strictEqual(result.error, AuthorizationError.CodeNotFound); @@ -170,14 +196,8 @@ describe("IsAuthorized", () => { }); const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b); - const result = await isAuthorized.invoke( - state, - tryAsCoreIndex(0), - BytesBlob.empty(), - AUTH_SERVICE_ID, - authCodeHash, - BytesBlob.empty(), - ); + const emptyPreimage = buildPackageAndFetchData(authCodeHash, BytesBlob.empty(), BytesBlob.empty()); + const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), emptyPreimage.fetchData); assert.strictEqual(result.isError, true); assert.strictEqual(result.error, AuthorizationError.CodeNotFound); diff --git a/packages/jam/in-core/is-authorized.ts b/packages/jam/in-core/is-authorized.ts index 1469c09dc..8b750d3dd 100644 --- a/packages/jam/in-core/is-authorized.ts +++ b/packages/jam/in-core/is-authorized.ts @@ -1,4 +1,4 @@ -import { type CodeHash, type CoreIndex, type ServiceGas, type ServiceId, tryAsServiceGas } from "@typeberry/block"; +import { type CoreIndex, type ServiceGas, tryAsServiceGas } from "@typeberry/block"; import { G_I, W_A } from "@typeberry/block/gp-constants.js"; import type { AuthorizerHash } from "@typeberry/block/refine-context.js"; import { BytesBlob } from "@typeberry/bytes"; @@ -7,8 +7,9 @@ import type { ChainSpec, PvmBackend } from "@typeberry/config"; import { PvmExecutor, ReturnStatus } from "@typeberry/executor"; import type { Blake2b } from "@typeberry/hash"; import type { State } from "@typeberry/state"; -import { IsAuthorizedFetchExternalities } from "@typeberry/transition/externalities/is-authorized-fetch-externalities.js"; +import type { WorkPackageFetchData } from "@typeberry/transition/externalities/fetch-externalities.js"; import { Result } from "@typeberry/utils"; +import { IsAuthorizedFetchExternalities } from "./externalities/index.js"; export enum AuthorizationError { /** BAD: authorizer code not found (service or preimage missing). */ @@ -44,11 +45,13 @@ export class IsAuthorized { async invoke( state: State, coreIndex: CoreIndex, - authToken: BytesBlob, - authCodeHost: ServiceId, - authCodeHash: CodeHash, - authConfiguration: BytesBlob, + packageFetchData: WorkPackageFetchData, ): Promise> { + const packageView = packageFetchData.packageView; + const authCodeHost = packageView.authCodeHost.materialize(); + const authCodeHash = packageView.authCodeHash.materialize(); + const authConfiguration = packageView.authConfiguration.materialize(); + // Look up the authorizer code from the auth code host service const service = state.getService(authCodeHost); // https://graypaper.fluffylabs.dev/#/ab2cdbd/2eca002eca00?v=0.7.2 @@ -77,10 +80,7 @@ export class IsAuthorized { } // Prepare fetch externalities and executor - const fetchExternalities = new IsAuthorizedFetchExternalities(this.chainSpec, { - authToken, - authConfiguration, - }); + const fetchExternalities = new IsAuthorizedFetchExternalities(this.chainSpec, packageFetchData); const executor = await PvmExecutor.createIsAuthorizedExecutor( authCodeHost, code, diff --git a/packages/jam/in-core/refine.ts b/packages/jam/in-core/refine.ts index 53b850b87..87bbd0551 100644 --- a/packages/jam/in-core/refine.ts +++ b/packages/jam/in-core/refine.ts @@ -1,7 +1,6 @@ import { type CoreIndex, type Segment, - type SegmentIndex, type ServiceGas, type ServiceId, tryAsCoreIndex, @@ -13,28 +12,25 @@ import type { WorkItem, WorkItemExtrinsic } from "@typeberry/block/work-item.js" import { WorkExecResult, WorkExecResultKind, WorkRefineLoad, WorkResult } from "@typeberry/block/work-result.js"; import { BytesBlob } from "@typeberry/bytes"; import { codec, Encoder } from "@typeberry/codec"; -import type { KnownSizeArray } from "@typeberry/collections"; import type { ChainSpec, PvmBackend } from "@typeberry/config"; import { PvmExecutor, type RefineHostCallExternalities, ReturnStatus, type ReturnValue } from "@typeberry/executor"; import { type Blake2b, HASH_SIZE } from "@typeberry/hash"; import { tryAsU32 } from "@typeberry/numbers"; import type { State } from "@typeberry/state"; -import { RefineFetchExternalities } from "@typeberry/transition/externalities/refine-fetch-externalities.js"; +import type { WorkPackageFetchData } from "@typeberry/transition/externalities/fetch-externalities.js"; import { assertNever, Result } from "@typeberry/utils"; -import { RefineExternalitiesImpl } from "./externalities/refine.js"; +import { + type ImportedSegment, + type PerWorkItem, + RefineExternalitiesImpl, + RefineFetchExternalities, +} from "./externalities/index.js"; export type RefineItemResult = { result: WorkResult; exports: readonly Segment[]; }; -export type PerWorkItem = KnownSizeArray; - -export type ImportedSegment = { - index: SegmentIndex; - data: Segment; -}; - enum ServiceCodeError { /** Service id is not found in the state. */ ServiceNotFound = 0, @@ -73,6 +69,7 @@ export class Refine { async invoke( state: State, lookupState: State, + packageFetchData: WorkPackageFetchData, idx: number, item: WorkItem, allImports: PerWorkItem, @@ -80,6 +77,7 @@ export class Refine { coreIndex: CoreIndex, workPackageHash: WorkPackageHash, exportOffset: number, + authorizerTrace: BytesBlob, ): Promise { const payloadHash = this.blake2b.hashBytes(item.payload); const baseResult = { @@ -118,12 +116,14 @@ export class Refine { const code = maybeCode.ok; const externalities = this.createRefineExternalities({ - payload: item.payload, + packageFetchData, + currentWorkItemIndex: idx, imports: allImports, extrinsics: allExtrinsics, currentServiceId: item.service, lookupState, exportOffset, + authorizerTrace, }); const executor = await PvmExecutor.createRefineExecutor(item.service, code, externalities, this.pvmBackend); @@ -229,15 +229,22 @@ export class Refine { } private createRefineExternalities(args: { - payload: BytesBlob; + packageFetchData: WorkPackageFetchData; + currentWorkItemIndex: number; imports: PerWorkItem; extrinsics: PerWorkItem; currentServiceId: ServiceId; lookupState: State; exportOffset: number; + authorizerTrace: BytesBlob; }): RefineHostCallExternalities { - // TODO [ToDr] Pass all required fetch data - const fetchExternalities = RefineFetchExternalities.new(this.chainSpec); + const fetchExternalities = new RefineFetchExternalities(this.chainSpec, { + packageData: args.packageFetchData, + currentWorkItemIndex: args.currentWorkItemIndex, + imports: args.imports, + extrinsics: args.extrinsics, + authorizerTrace: args.authorizerTrace, + }); const refine = RefineExternalitiesImpl.create({ currentServiceId: args.currentServiceId, lookupState: args.lookupState, diff --git a/packages/jam/transition/externalities/fetch-externalities.ts b/packages/jam/transition/externalities/fetch-externalities.ts index 3ceff7acf..0daac9b02 100644 --- a/packages/jam/transition/externalities/fetch-externalities.ts +++ b/packages/jam/transition/externalities/fetch-externalities.ts @@ -1,10 +1,13 @@ +import { type CodeHash, reencodeAsView, type ServiceGas, type ServiceId } from "@typeberry/block"; import { G_I, MAX_REPORT_DEPENDENCIES, O, Q, T, W_A, W_B, W_C, W_M, W_T, W_X } from "@typeberry/block/gp-constants.js"; -import { MAX_NUMBER_OF_WORK_ITEMS } from "@typeberry/block/work-package.js"; -import type { BytesBlob } from "@typeberry/bytes"; -import { codec, Encoder } from "@typeberry/codec"; +import type { WorkItem } from "@typeberry/block/work-item.js"; +import { MAX_NUMBER_OF_WORK_ITEMS, WorkPackage, type WorkPackageView } from "@typeberry/block/work-package.js"; +import { BytesBlob } from "@typeberry/bytes"; +import { codec, Decoder, type DescribedBy, Encoder, SequenceView } from "@typeberry/codec"; import type { ChainSpec } from "@typeberry/config"; +import { HASH_SIZE } from "@typeberry/hash"; import { PendingTransfer } from "@typeberry/jam-host-calls"; -import { tryAsU16, tryAsU32, tryAsU64 } from "@typeberry/numbers"; +import { tryAsU16, tryAsU32, tryAsU64, type U64 } from "@typeberry/numbers"; import { BASE_SERVICE_BALANCE, ELECTIVE_BYTE_BALANCE, @@ -124,3 +127,63 @@ export function getEncodedConstants(chainSpec: ChainSpec) { return encodedConsts; } + +/** + * `S(w)` — work-item summary used by fetch in both the IsAuthorized + * and Refine contexts. + * + * https://graypaper.fluffylabs.dev/#/ab2cdbd/31fc0231fc02?v=0.7.2 + */ +const WORK_ITEM_SUMMARY_CODEC = codec.object({ + service: codec.u32.asOpaque(), + codeHash: codec.bytes(HASH_SIZE).asOpaque(), + refineGasLimit: codec.u64.asOpaque(), + accumulateGasLimit: codec.u64.asOpaque(), + exportCount: codec.u16, + importSegmentsCount: codec.u16, + extrinsicCount: codec.u16, + payloadLength: codec.u32, +}); +type WorkItemSummary = DescribedBy; +type WorkItemSummaryView = DescribedBy; + +export function encodeWorkItemSummary(item: WorkItem): BytesBlob { + return Encoder.encodeObject(WORK_ITEM_SUMMARY_CODEC, { + service: item.service, + codeHash: item.codeHash, + refineGasLimit: item.refineGasLimit, + accumulateGasLimit: item.accumulateGasLimit, + exportCount: item.exportCount, + importSegmentsCount: tryAsU16(item.importSegments.length), + extrinsicCount: tryAsU16(item.extrinsic.length), + payloadLength: tryAsU32(item.payload.length), + }); +} + +/** Encoded work package data for fetch, shared between `IsAuthorized` and `Refine` fetchers. */ +export type WorkPackageFetchData = { + /** Lazy view over the encoded work package. */ + packageView: WorkPackageView; + /** SequenceView over the concatenated S(w) summaries. */ + workItemSummaries: SequenceView; +}; + +/** Eagerly build the per-package fetch views. */ +export function buildWorkPackageFetchData(chainSpec: ChainSpec, workPackage: WorkPackage): WorkPackageFetchData { + const packageView = reencodeAsView(WorkPackage.Codec, workPackage, chainSpec); + + const summariesBlob = BytesBlob.blobFromParts(workPackage.items.map((i) => encodeWorkItemSummary(i).raw)); + + const workItemSummaries = new SequenceView( + Decoder.fromBytesBlob(summariesBlob), + WORK_ITEM_SUMMARY_CODEC, + workPackage.items.length, + ); + + return { packageView, workItemSummaries }; +} + +/** Converts u64 value taken from a register into valid index of array of given `length`. */ +export function u64ToArrayIndex(v: U64, len: number): number | null { + return v < BigInt(len) ? Number(v) : null; +} diff --git a/packages/jam/transition/externalities/index.ts b/packages/jam/transition/externalities/index.ts index c2b85dc83..c32b5d110 100644 --- a/packages/jam/transition/externalities/index.ts +++ b/packages/jam/transition/externalities/index.ts @@ -1,5 +1,3 @@ export * from "./accumulate-externalities.js"; export * from "./accumulate-fetch-externalities.js"; export * from "./fetch-externalities.js"; -export * from "./is-authorized-fetch-externalities.js"; -export * from "./refine-fetch-externalities.js"; diff --git a/packages/jam/transition/externalities/is-authorized-fetch-externalities.ts b/packages/jam/transition/externalities/is-authorized-fetch-externalities.ts deleted file mode 100644 index effefbfc9..000000000 --- a/packages/jam/transition/externalities/is-authorized-fetch-externalities.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { BytesBlob } from "@typeberry/bytes"; -import type { ChainSpec } from "@typeberry/config"; -import { general } from "@typeberry/jam-host-calls"; -import type { U64 } from "@typeberry/numbers"; -import { getEncodedConstants } from "./fetch-externalities.js"; - -export class IsAuthorizedFetchExternalities implements general.IIsAuthorizedFetch { - readonly context = general.FetchContext.IsAuthorized; - - constructor( - private readonly chainSpec: ChainSpec, - private readonly params: { - authToken: BytesBlob; - authConfiguration: BytesBlob; - }, - ) {} - - constants(): BytesBlob { - return getEncodedConstants(this.chainSpec); - } - - // TODO [ToDr] Return encoded work package E(p) - workPackage(): BytesBlob { - return BytesBlob.empty(); - } - - authConfiguration(): BytesBlob { - return this.params.authConfiguration; - } - - authToken(): BytesBlob { - return this.params.authToken; - } - - // TODO [ToDr] Return encoded refinement context - refineContext(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] Return encoded work items - allWorkItems(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] Return single work item summary - oneWorkItem(_workItem: U64): BytesBlob | null { - return null; - } - - // TODO [ToDr] Return work item payload - workItemPayload(_workItem: U64): BytesBlob | null { - return null; - } -} diff --git a/packages/jam/transition/externalities/refine-fetch-externalities.test.ts b/packages/jam/transition/externalities/refine-fetch-externalities.test.ts deleted file mode 100644 index f27fbb92a..000000000 --- a/packages/jam/transition/externalities/refine-fetch-externalities.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import assert from "node:assert"; -import { describe, it } from "node:test"; - -import { type ChainSpec, fullChainSpec, tinyChainSpec } from "@typeberry/config"; -import { RefineFetchExternalities } from "./refine-fetch-externalities.js"; - -describe("RefineFetchExternalities", () => { - const prepareRefineData = ({ chainSpec }: { chainSpec?: ChainSpec } = {}) => { - const defaultChainSpec = tinyChainSpec; - return RefineFetchExternalities.new(chainSpec ?? defaultChainSpec); - }; - - it("should return different constants for different chain specs", () => { - const tinyFetchExternalities = prepareRefineData({ chainSpec: tinyChainSpec }); - const fullFetchExternalities = prepareRefineData({ chainSpec: fullChainSpec }); - - const tinyConstants = tinyFetchExternalities.constants(); - const fullConstants = fullFetchExternalities.constants(); - - assert.notStrictEqual(tinyConstants.length, 0); - assert.notStrictEqual(fullConstants.length, 0); - assert.notDeepStrictEqual(tinyConstants, fullConstants); - }); - - // Pending implementation — these should assert against real fixture values once - // RefineFetchExternalities accepts and exposes the required refine inputs. - it.todo("should return entropy (H₀ header hash of anchor block)"); - it.todo("should return authorizer trace"); - it.todo("should return work item extrinsic"); - it.todo("should return work item import"); - it.todo("should return work package"); - it.todo("should return authorizer"); - it.todo("should return authorization token"); - it.todo("should return refine context"); - it.todo("should return all work items"); - it.todo("should return one work item"); - it.todo("should return work item payload"); -}); diff --git a/packages/jam/transition/externalities/refine-fetch-externalities.ts b/packages/jam/transition/externalities/refine-fetch-externalities.ts deleted file mode 100644 index 25bc98ef6..000000000 --- a/packages/jam/transition/externalities/refine-fetch-externalities.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { EntropyHash } from "@typeberry/block"; -import { Bytes, BytesBlob } from "@typeberry/bytes"; -import type { ChainSpec } from "@typeberry/config"; -import { HASH_SIZE } from "@typeberry/hash"; -import { general } from "@typeberry/jam-host-calls"; -import type { U64 } from "@typeberry/numbers"; -import { getEncodedConstants } from "./fetch-externalities.js"; - -export class RefineFetchExternalities implements general.IRefineFetch { - readonly context = general.FetchContext.Refine; - - static new(chainSpec: ChainSpec) { - return new RefineFetchExternalities(chainSpec); - } - - private constructor(private readonly chainSpec: ChainSpec) {} - - constants(): BytesBlob { - return getEncodedConstants(this.chainSpec); - } - - // Returns H₀ (zero hash) - entropy(): EntropyHash { - return Bytes.zero(HASH_SIZE).asOpaque(); - } - - // TODO [ToDr] implement - authorizerTrace(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] implement - workItemExtrinsic(_workItem: U64 | null, _index: U64): BytesBlob | null { - return null; - } - - // TODO [ToDr] implement - workItemImport(_workItem: U64 | null, _index: U64): BytesBlob | null { - return null; - } - - // TODO [ToDr] implement - workPackage(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] implement - authConfiguration(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] implement - authToken(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] implement - refineContext(): BytesBlob { - return BytesBlob.empty(); - } - - // TODO [ToDr] implement - allWorkItems(): BytesBlob { - return BytesBlob.empty(); - } - - oneWorkItem(_workItem: U64): BytesBlob | null { - return null; - } - - workItemPayload(_workItem: U64): BytesBlob | null { - return null; - } -}