From 4217fb75ca698ae92d4bed9c6b23d9c733edba5f Mon Sep 17 00:00:00 2001 From: Sebastian Koszuta Date: Tue, 27 Jan 2026 12:08:38 +0100 Subject: [PATCH 1/5] preimages and lookup history validation --- bin/cli/types/config.ts | 4 + bin/cli/utils/config-validator.test.ts | 291 +++++++++++++++++++++++++ bin/cli/utils/config-validator.ts | 7 + 3 files changed, 302 insertions(+) diff --git a/bin/cli/types/config.ts b/bin/cli/types/config.ts index a72e1a7..2b6beab 100644 --- a/bin/cli/types/config.ts +++ b/bin/cli/types/config.ts @@ -39,6 +39,10 @@ export interface ServiceDeploymentConfig { id?: number; /** Storage key-value pairs */ storage?: Record; + /** Preimages map: 32-byte preimage hash (0x hex) -> blob (0x hex) */ + preimages?: Record; + /** Lookup history map: 32-byte preimage hash (0x hex) -> integer time slots */ + lookup_history?: Record; /** Optional service account info overrides */ info?: ServiceAccountInfoConfig; } diff --git a/bin/cli/utils/config-validator.test.ts b/bin/cli/utils/config-validator.test.ts index f1f2e41..2e9c426 100644 --- a/bin/cli/utils/config-validator.test.ts +++ b/bin/cli/utils/config-validator.test.ts @@ -520,6 +520,297 @@ describe("Validate Build Config", () => { }); }); + describe("Preimages Config Validation", () => { + test("Should parse valid preimages config", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + preimages: { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0xdeadbeef", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": "0xadadadadad", + }, + }, + }, + }, + }; + + const result = validateBuildConfig(config); + expect(result.deployment?.services?.["auth-service"]?.preimages).toEqual({ + "0x0000000000000000000000000000000000000000000000000000000000000001": "0xdeadbeef", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": "0xadadadadad", + }); + }); + + test("Should reject preimages with hash missing 0x prefix", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + preimages: { + "0000000000000000000000000000000000000000000000000000000000000001": "0x1234", + }, + }, + }, + }, + }; + + expect(() => validateBuildConfig(config)).toThrow(); + }); + + test("Should reject preimages with blob missing 0x prefix", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + preimages: { + "0x0000000000000000000000000000000000000000000000000000000000000001": "deadbeef", + }, + }, + }, + }, + }; + + expect(() => validateBuildConfig(config)).toThrow(); + }); + + test("Should reject preimages with blob consisting of an uneven number of characters", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + preimages: { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0xabc", + }, + }, + }, + }, + }; + + expect(() => validateBuildConfig(config)).toThrow(); + }); + + test("Should reject preimages where hash is not 32 bytes long", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + preimages: { + "0x00000000000000000000000000000001": "0xdeadbeef", // 16 bytes instead of 32 + }, + }, + }, + }, + }; + + expect(() => validateBuildConfig(config)).toThrow(); + }); + }); + + describe("Lookup History Config Validation", () => { + test("Should parse valid lookup_history config", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + lookup_history: { + "0x0000000000000000000000000000000000000000000000000000000000000001": [100, 200, 300], + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": [42], + }, + }, + }, + }, + }; + + const result = validateBuildConfig(config); + expect(result.deployment?.services?.["auth-service"]?.lookup_history).toEqual({ + "0x0000000000000000000000000000000000000000000000000000000000000001": [100, 200, 300], + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": [42], + }); + }); + + test("Should accept lookup_history with empty array", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + lookup_history: { + "0x0000000000000000000000000000000000000000000000000000000000000001": [], + }, + }, + }, + }, + }; + + const result = validateBuildConfig(config); + expect(result.deployment?.services?.["auth-service"]?.lookup_history).toEqual({ + "0x0000000000000000000000000000000000000000000000000000000000000001": [], + }); + }); + + test("Should accept lookup_history with 1, 2, and 3 time slots", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + lookup_history: { + "0x0000000000000000000000000000000000000000000000000000000000000001": [1], + "0x0000000000000000000000000000000000000000000000000000000000000002": [1, 2], + "0x0000000000000000000000000000000000000000000000000000000000000003": [1, 2, 3], + }, + }, + }, + }, + }; + + const result = validateBuildConfig(config); + const lookupHistory = result.deployment?.services?.["auth-service"]?.lookup_history; + expect(lookupHistory?.["0x0000000000000000000000000000000000000000000000000000000000000001"]).toEqual([1]); + expect(lookupHistory?.["0x0000000000000000000000000000000000000000000000000000000000000002"]).toEqual([1, 2]); + expect(lookupHistory?.["0x0000000000000000000000000000000000000000000000000000000000000003"]).toEqual([1, 2, 3]); + }); + + test("Should reject lookup_history with more than 3 time slots", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + lookup_history: { + "0x0000000000000000000000000000000000000000000000000000000000000001": [1, 2, 3, 4], + }, + }, + }, + }, + }; + + expect(() => validateBuildConfig(config)).toThrow(); + }); + + test("Should reject lookup_history with non-integer time slot values", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + lookup_history: { + "0x0000000000000000000000000000000000000000000000000000000000000001": [100.5, 200], + }, + }, + }, + }, + }; + + expect(() => validateBuildConfig(config)).toThrow(); + }); + + + test("Should accept lookup_history with negative time slot values", () => { + const config = { + services: [ + { + path: "./services/auth.ts", + name: "auth-service", + sdk: "jam-sdk-0.1.26", + }, + ], + deployment: { + spawn: "local", + services: { + "auth-service": { + lookup_history: { + "0x0000000000000000000000000000000000000000000000000000000000000001": [-10, 0, 100], + }, + }, + }, + }, + }; + + const result = validateBuildConfig(config); + expect(result.deployment?.services?.["auth-service"]?.lookup_history).toEqual({ + "0x0000000000000000000000000000000000000000000000000000000000000001": [-10, 0, 100], + }); + }); + }); + describe("Service Info Config Validation", () => { test("Should parse valid info config with all fields", () => { const config = { diff --git a/bin/cli/utils/config-validator.ts b/bin/cli/utils/config-validator.ts index e37bf51..9d760ee 100644 --- a/bin/cli/utils/config-validator.ts +++ b/bin/cli/utils/config-validator.ts @@ -9,6 +9,11 @@ const MAX_U64 = 18_446_744_073_709_551_615n; const u64Schema = () => z.union([z.bigint().min(0n).max(MAX_U64), z.number().int().min(0)]).transform((val) => BigInt(val)); const u32Schema = () => z.number().int().min(0).max(MAX_U32); +const preimageHashSchema = () => + z + .string() + .regex(/^0x[0-9a-fA-F]+$/) + .length(66); // jammin.build.yml schema @@ -33,6 +38,8 @@ const ServiceConfigSchema = z.object({ const ServiceDeploymentConfigSchema = z.object({ id: u32Schema().optional(), storage: z.record(z.string(), z.string()).optional(), + preimages: z.record(preimageHashSchema(), z.string().regex(/^0x([0-9a-fA-F]{2})+$/)).optional(), + lookup_history: z.record(preimageHashSchema(), z.array(z.int()).max(3)).optional(), info: z .object({ balance: u64Schema().optional(), From 82b988dfa797ed1e698a4c8d173d88c4a68519c6 Mon Sep 17 00:00:00 2001 From: Sebastian Koszuta Date: Tue, 27 Jan 2026 13:05:09 +0100 Subject: [PATCH 2/5] passing data from deploy command to genesis generator --- bin/cli/src/commands/deploy-command.ts | 9 ++++- bin/cli/utils/config-validator.test.ts | 1 - .../util/generate-service-output.ts | 36 ++++++++++++++++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/bin/cli/src/commands/deploy-command.ts b/bin/cli/src/commands/deploy-command.ts index 925b6c1..435c807 100644 --- a/bin/cli/src/commands/deploy-command.ts +++ b/bin/cli/src/commands/deploy-command.ts @@ -87,7 +87,14 @@ Examples: const deploymentConfig = serviceDeploymentConfigs[service.name]; const serviceId = deploymentConfig?.id ?? getNextAvailableId(); - return generateServiceOutput(jamFile, serviceId, deploymentConfig?.storage, deploymentConfig?.info); + return generateServiceOutput( + jamFile, + serviceId, + deploymentConfig?.storage, + deploymentConfig?.info, + deploymentConfig?.preimages, + deploymentConfig?.lookup_history, + ); }), ); diff --git a/bin/cli/utils/config-validator.test.ts b/bin/cli/utils/config-validator.test.ts index 2e9c426..c37e34c 100644 --- a/bin/cli/utils/config-validator.test.ts +++ b/bin/cli/utils/config-validator.test.ts @@ -782,7 +782,6 @@ describe("Validate Build Config", () => { expect(() => validateBuildConfig(config)).toThrow(); }); - test("Should accept lookup_history with negative time slot values", () => { const config = { services: [ diff --git a/packages/jammin-sdk/util/generate-service-output.ts b/packages/jammin-sdk/util/generate-service-output.ts index 53a1afb..7ffef82 100644 --- a/packages/jammin-sdk/util/generate-service-output.ts +++ b/packages/jammin-sdk/util/generate-service-output.ts @@ -1,14 +1,24 @@ import { resolve } from "node:path"; -import { type ServiceId as ServiceIdType, tryAsServiceGas, tryAsServiceId, tryAsTimeSlot } from "@typeberry/lib/block"; -import { BytesBlob } from "@typeberry/lib/bytes"; +import { + type preimage, + type ServiceId as ServiceIdType, + tryAsServiceGas, + tryAsServiceId, + tryAsTimeSlot, +} from "@typeberry/lib/block"; +import { Bytes, BytesBlob } from "@typeberry/lib/bytes"; +import { HASH_SIZE } from "@typeberry/lib/hash"; import { tryAsU32, tryAsU64 } from "@typeberry/lib/numbers"; -import { ServiceId } from "../types.js"; +import { type LookupHistorySlots, type ServiceAccountInfo, tryAsLookupHistorySlots } from "@typeberry/lib/state"; +import { ServiceId, Slot } from "../types.js"; export interface ServiceBuildOutput { id: ServiceIdType; code: BytesBlob; storage?: Record; - info?: Partial; + info?: Partial; + preimages?: Map; + lookupHistory?: Map; } export async function generateServiceOutput( @@ -26,6 +36,8 @@ export async function generateServiceOutput( lastAccumulation?: number; parentService?: number; }, + preimages?: Record, + lookupHistory?: Record, ): Promise { const absolutePath = resolve(jamFilePath); const fileBytes = await Bun.file(absolutePath).bytes(); @@ -45,10 +57,26 @@ export async function generateServiceOutput( }).filter(([_, value]) => value !== undefined), ); + const preimagesMap = new Map( + Object.entries(preimages ?? {}).map(([hash, blob]) => [ + Bytes.parseBytes(hash, HASH_SIZE).asOpaque(), + BytesBlob.blobFromString(blob), + ]), + ); + + const lookupHistoryMap = new Map( + Object.entries(lookupHistory ?? {}).map(([hash, slots]) => [ + Bytes.parseBytes(hash, HASH_SIZE).asOpaque(), + tryAsLookupHistorySlots(slots.map((slot) => Slot(slot))), + ]), + ); + return { id: ServiceId(serviceId), code, storage, info: serviceAccountInfo, + preimages: preimagesMap, + lookupHistory: lookupHistoryMap, }; } From cc741df264294f4e674a597135aa4c5b503cc9ca Mon Sep 17 00:00:00 2001 From: Sebastian Koszuta Date: Thu, 29 Jan 2026 11:33:04 +0100 Subject: [PATCH 3/5] adding preimages to genesis state --- .../genesis-state-generator.test.ts | 234 +++++++++++++++--- .../jammin-sdk/genesis-state-generator.ts | 40 +++ .../util/generate-service-output.ts | 7 +- 3 files changed, 242 insertions(+), 39 deletions(-) diff --git a/packages/jammin-sdk/genesis-state-generator.test.ts b/packages/jammin-sdk/genesis-state-generator.test.ts index 592a7fc..86b4d1f 100644 --- a/packages/jammin-sdk/genesis-state-generator.test.ts +++ b/packages/jammin-sdk/genesis-state-generator.test.ts @@ -1,41 +1,203 @@ import { describe, expect, test } from "bun:test"; -import { BytesBlob } from "@typeberry/lib/bytes"; -import { generateGenesis, type ServiceBuildOutput, toJip4Schema } from "./genesis-state-generator"; -import { ServiceId } from "./types"; +import { Bytes, BytesBlob } from "@typeberry/lib/bytes"; +import { HashDictionary } from "@typeberry/lib/collections"; +import { Blake2b, HASH_SIZE } from "@typeberry/lib/hash"; +import { type StorageKey, tryAsLookupHistorySlots } from "@typeberry/lib/state"; +import { asOpaqueType } from "@typeberry/lib/utils"; +import { generateGenesis, generateState, toJip4Schema } from "./genesis-state-generator"; +import { Gas, ServiceId, Slot, U32, U64 } from "./types"; +import type { ServiceBuildOutput } from "./util/generate-service-output"; + +const blake2b = await Blake2b.createHasher(); describe("genesis-generator", () => { - const services: ServiceBuildOutput[] = [ - { - id: ServiceId(42), - code: BytesBlob.parseBlob( - "0x5000156a616d2d626f6f7473747261702d7365727669636506302e312e32370a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e20350028000002000020000800", - ), - }, - { - id: ServiceId(7), - code: BytesBlob.parseBlob( - "0x509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f8235742f", - ), - }, - ]; - - test("should generate genesis file", async () => { - // when - const genesis = toJip4Schema(generateGenesis(services)); - - // then - expect(genesis.id).toBe("testnet"); - expect(genesis.bootnodes).toEqual([]); - expect(genesis.genesis_header).toBeDefined(); - expect(genesis.genesis_state).toBeDefined(); - expect(Array.isArray(genesis.genesis_state)).toBe(true); - expect(genesis.genesis_state.length).toBeGreaterThan(0); - - const stateValues = genesis.genesis_state.map((entry) => entry[1]); - const serviceCodeHex = services[0]?.code.toString().substring(2); - const serviceCodeHex1 = services[1]?.code.toString().substring(2); - - expect(stateValues.some((v) => v?.includes(serviceCodeHex ?? ""))).toBe(true); - expect(stateValues.some((v) => v?.includes(serviceCodeHex1 ?? ""))).toBe(true); + const basicService: ServiceBuildOutput = { + id: ServiceId(42), + code: BytesBlob.parseBlob( + "0x5000156a616d2d626f6f7473747261702d7365727669636506302e312e32370a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e20350028000002000020000800", + ), + }; + + const largerCodeService: ServiceBuildOutput = { + id: ServiceId(7), + code: BytesBlob.parseBlob( + "0x509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f82357426f2313559a271d6782dc00197b379f79cbe3c6a1e72f61f7b592c509f8235742f", + ), + }; + + describe("generateGenesis", () => { + test("should generate genesis with basic services", async () => { + const services = [basicService, largerCodeService]; + + const genesis = toJip4Schema(generateGenesis(services)); + + expect(genesis.id).toBe("testnet"); + expect(genesis.bootnodes).toEqual([]); + expect(genesis.genesis_header).toBeDefined(); + expect(genesis.genesis_state).toBeDefined(); + expect(Array.isArray(genesis.genesis_state)).toBe(true); + expect(genesis.genesis_state.length).toBeGreaterThan(0); + + const stateValues = genesis.genesis_state.map((entry) => entry[1]); + const serviceCodeHex = services[0]?.code.toString().substring(2); + const serviceCodeHex1 = services[1]?.code.toString().substring(2); + + expect(stateValues.some((v) => v?.includes(serviceCodeHex ?? ""))).toBe(true); + expect(stateValues.some((v) => v?.includes(serviceCodeHex1 ?? ""))).toBe(true); + }); + + test("should generate genesis with empty services array", async () => { + const genesis = toJip4Schema(generateGenesis([])); + + expect(genesis.id).toBe("testnet"); + expect(genesis.genesis_header).toBeDefined(); + expect(genesis.genesis_state).toBeDefined(); + }); + }); + + describe("generateState with storage", () => { + test("should store and retrieve storage items", async () => { + const storageKey1Str = "key1"; + const storageKey2Str = "key2"; + const storageValue1Str = "value1"; + const storageValue2Str = "value2"; + + const storageKey1: StorageKey = asOpaqueType(BytesBlob.blobFromString(storageKey1Str)); + const storageKey2: StorageKey = asOpaqueType(BytesBlob.blobFromString(storageKey2Str)); + const expectedValue1 = BytesBlob.blobFromString(storageValue1Str); + const expectedValue2 = BytesBlob.blobFromString(storageValue2Str); + + const serviceWithStorage: ServiceBuildOutput = { + ...basicService, + id: ServiceId(100), + storage: { + [storageKey1Str]: storageValue1Str, + [storageKey2Str]: storageValue2Str, + }, + }; + + const state = generateState([serviceWithStorage]); + + const service = state.services.get(ServiceId(100)); + expect(service).toBeDefined(); + + const retrievedValue1 = service?.getStorage(storageKey1); + const retrievedValue2 = service?.getStorage(storageKey2); + + expect(retrievedValue1?.toString()).toBe(expectedValue1.toString()); + expect(retrievedValue2?.toString()).toBe(expectedValue2.toString()); + }); + + test("should calculate storage utilisation correctly", async () => { + const serviceWithStorage: ServiceBuildOutput = { + ...basicService, + id: ServiceId(102), + storage: { + key: "value", + }, + }; + + const state = generateState([serviceWithStorage]); + + const service = state.services.get(ServiceId(102)); + expect(service).toBeDefined(); + + const info = service?.getInfo(); + expect(info?.storageUtilisationCount).toBe(U32(3)); + expect(info?.storageUtilisationBytes).toEqual(U64(217)); + }); + }); + + describe("generateState with custom service info", () => { + test("should apply custom balance", async () => { + const customBalance = U64(5_000_000n); + const serviceWithInfo: ServiceBuildOutput = { + ...basicService, + id: ServiceId(200), + info: { + balance: customBalance, + }, + }; + + const state = generateState([serviceWithInfo]); + + const serviceAccount = state.services.get(ServiceId(200)); + expect(serviceAccount).toBeDefined(); + expect(serviceAccount?.getInfo().balance).toEqual(customBalance); + }); + + test("should apply custom gas settings", async () => { + const customAccumulateGas = Gas(100n); + const customOnTransferGas = Gas(50n); + const serviceWithGas: ServiceBuildOutput = { + ...basicService, + id: ServiceId(201), + info: { + accumulateMinGas: customAccumulateGas, + onTransferMinGas: customOnTransferGas, + }, + }; + + const state = generateState([serviceWithGas]); + + const serviceAccount = state.services.get(ServiceId(201)); + expect(serviceAccount).toBeDefined(); + expect(serviceAccount?.getInfo().accumulateMinGas).toEqual(customAccumulateGas); + expect(serviceAccount?.getInfo().onTransferMinGas).toEqual(customOnTransferGas); + }); + }); + + describe("generateState with preimages and lookup history", () => { + test("should include additional preimages", async () => { + const preimageBlob = BytesBlob.parseBlob("0xaabbccdd"); + const preimageHash = blake2b.hashBytes(preimageBlob).asOpaque(); + + const serviceWithPreimages: ServiceBuildOutput = { + ...basicService, + id: ServiceId(300), + preimages: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), + lookupHistory: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0)])]]), + }; + + const state = generateState([serviceWithPreimages]); + + expect(state).toBeDefined(); + const serviceAccount = state.services.get(ServiceId(300)); + expect(serviceAccount).toBeDefined(); + }); + + test("should handle multiple slots in lookup history", async () => { + const preimageBlob = BytesBlob.parseBlob("0x11223344"); + const preimageHash = blake2b.hashBytes(preimageBlob).asOpaque(); + + const serviceWithMultiSlots: ServiceBuildOutput = { + ...basicService, + id: ServiceId(301), + preimages: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), + lookupHistory: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0), Slot(1), Slot(2)])]]), + }; + + const state = generateState([serviceWithMultiSlots]); + + expect(state).toBeDefined(); + const serviceAccount = state.services.get(ServiceId(301)); + expect(serviceAccount).toBeDefined(); + }); + + test("should throw error when preimage blob is missing for lookup history entry", async () => { + const missingHash = Bytes.parseBytes( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + HASH_SIZE, + ).asOpaque(); + + const serviceWithMissingPreimage: ServiceBuildOutput = { + ...basicService, + id: ServiceId(302), + preimages: HashDictionary.fromEntries([]), // Empty - no preimage for the hash + lookupHistory: new Map([[missingHash, tryAsLookupHistorySlots([Slot(0)])]]), + }; + + expect(() => generateState([serviceWithMissingPreimage])).toThrow("Preimage blob not found for hash"); + }); }); }); diff --git a/packages/jammin-sdk/genesis-state-generator.ts b/packages/jammin-sdk/genesis-state-generator.ts index be79665..8493489 100644 --- a/packages/jammin-sdk/genesis-state-generator.ts +++ b/packages/jammin-sdk/genesis-state-generator.ts @@ -145,6 +145,46 @@ export function generateState(services: ServiceBuildOutput[]): InMemoryState { lookupHistory, }), ); + + // add preimages and lookup history + if (service.lookupHistory) { + const preimageUpdates = update.preimages?.get(serviceId) ?? []; + + for (const [hash, slots] of service.lookupHistory) { + const preimageBlob = service.preimages?.get(hash); + + if (!preimageBlob) { + throw new Error(`Preimage blob not found for hash ${hash}`); + } + + // request preimage + const lookupHistory = new LookupHistoryItem(hash, U32(preimageBlob.length), tryAsLookupHistorySlots([])); + preimageUpdates.push(UpdatePreimage.updateOrAdd({ lookupHistory })); + + // provide + if (slots.length >= 1) { + if (preimageBlob) { + preimageUpdates.push( + UpdatePreimage.provide({ + preimage: PreimageItem.create({ hash, blob: preimageBlob }), + providedFor: serviceId, + slot: slots[0], + }), + ); + } + } + + // update lookup history + if (slots.length > 1) { + const lookupHistory = new LookupHistoryItem(hash, U32(preimageBlob.length), slots); + preimageUpdates.push(UpdatePreimage.updateOrAdd({ lookupHistory })); + } + } + + if (preimageUpdates.length > 0) { + update.preimages?.set(serviceId, preimageUpdates); + } + } } memState.applyUpdate(update); diff --git a/packages/jammin-sdk/util/generate-service-output.ts b/packages/jammin-sdk/util/generate-service-output.ts index 7ffef82..90baeb5 100644 --- a/packages/jammin-sdk/util/generate-service-output.ts +++ b/packages/jammin-sdk/util/generate-service-output.ts @@ -7,6 +7,7 @@ import { tryAsTimeSlot, } from "@typeberry/lib/block"; import { Bytes, BytesBlob } from "@typeberry/lib/bytes"; +import { HashDictionary } from "@typeberry/lib/collections"; import { HASH_SIZE } from "@typeberry/lib/hash"; import { tryAsU32, tryAsU64 } from "@typeberry/lib/numbers"; import { type LookupHistorySlots, type ServiceAccountInfo, tryAsLookupHistorySlots } from "@typeberry/lib/state"; @@ -17,7 +18,7 @@ export interface ServiceBuildOutput { code: BytesBlob; storage?: Record; info?: Partial; - preimages?: Map; + preimages?: HashDictionary; lookupHistory?: Map; } @@ -57,7 +58,7 @@ export async function generateServiceOutput( }).filter(([_, value]) => value !== undefined), ); - const preimagesMap = new Map( + const preimagesDict = HashDictionary.fromEntries( Object.entries(preimages ?? {}).map(([hash, blob]) => [ Bytes.parseBytes(hash, HASH_SIZE).asOpaque(), BytesBlob.blobFromString(blob), @@ -76,7 +77,7 @@ export async function generateServiceOutput( code, storage, info: serviceAccountInfo, - preimages: preimagesMap, + preimages: preimagesDict, lookupHistory: lookupHistoryMap, }; } From d4fd9aaa81f9b3fa15b3394b9616e628b0f71427 Mon Sep 17 00:00:00 2001 From: Sebastian Koszuta Date: Fri, 30 Jan 2026 11:38:49 +0100 Subject: [PATCH 4/5] renaming, calculating storage, improved cfg validation --- bin/cli/src/commands/deploy-command.ts | 4 +- bin/cli/types/config.ts | 8 +- bin/cli/utils/config-validator.test.ts | 69 ++++++++------- bin/cli/utils/config-validator.ts | 83 ++++++++++--------- .../genesis-state-generator.test.ts | 42 +++++++--- .../jammin-sdk/genesis-state-generator.ts | 52 +++++++----- .../util/generate-service-output.ts | 20 ++--- 7 files changed, 159 insertions(+), 119 deletions(-) diff --git a/bin/cli/src/commands/deploy-command.ts b/bin/cli/src/commands/deploy-command.ts index 435c807..602d85c 100644 --- a/bin/cli/src/commands/deploy-command.ts +++ b/bin/cli/src/commands/deploy-command.ts @@ -92,8 +92,8 @@ Examples: serviceId, deploymentConfig?.storage, deploymentConfig?.info, - deploymentConfig?.preimages, - deploymentConfig?.lookup_history, + deploymentConfig?.preimageBlobs, + deploymentConfig?.preimageRequests, ); }), ); diff --git a/bin/cli/types/config.ts b/bin/cli/types/config.ts index 2b6beab..d10369b 100644 --- a/bin/cli/types/config.ts +++ b/bin/cli/types/config.ts @@ -39,10 +39,10 @@ export interface ServiceDeploymentConfig { id?: number; /** Storage key-value pairs */ storage?: Record; - /** Preimages map: 32-byte preimage hash (0x hex) -> blob (0x hex) */ - preimages?: Record; - /** Lookup history map: 32-byte preimage hash (0x hex) -> integer time slots */ - lookup_history?: Record; + /** Preimage blobs map: 32-byte preimage hash (0x hex) -> blob (0x hex) */ + preimageBlobs?: Record; + /** Preimage requests map: 32-byte preimage hash (0x hex) -> integer time slots */ + preimageRequests?: Record; /** Optional service account info overrides */ info?: ServiceAccountInfoConfig; } diff --git a/bin/cli/utils/config-validator.test.ts b/bin/cli/utils/config-validator.test.ts index c37e34c..b09682c 100644 --- a/bin/cli/utils/config-validator.test.ts +++ b/bin/cli/utils/config-validator.test.ts @@ -520,8 +520,8 @@ describe("Validate Build Config", () => { }); }); - describe("Preimages Config Validation", () => { - test("Should parse valid preimages config", () => { + describe("Preimage Blobs Config Validation", () => { + test("Should parse valid preimage_blobs config", () => { const config = { services: [ { @@ -534,7 +534,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - preimages: { + preimage_blobs: { "0x0000000000000000000000000000000000000000000000000000000000000001": "0xdeadbeef", "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": "0xadadadadad", }, @@ -544,13 +544,13 @@ describe("Validate Build Config", () => { }; const result = validateBuildConfig(config); - expect(result.deployment?.services?.["auth-service"]?.preimages).toEqual({ + expect(result.deployment?.services?.["auth-service"]?.preimageBlobs).toEqual({ "0x0000000000000000000000000000000000000000000000000000000000000001": "0xdeadbeef", "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": "0xadadadadad", }); }); - test("Should reject preimages with hash missing 0x prefix", () => { + test("Should reject preimage_blobs with hash missing 0x prefix", () => { const config = { services: [ { @@ -563,7 +563,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - preimages: { + preimage_blobs: { "0000000000000000000000000000000000000000000000000000000000000001": "0x1234", }, }, @@ -574,7 +574,7 @@ describe("Validate Build Config", () => { expect(() => validateBuildConfig(config)).toThrow(); }); - test("Should reject preimages with blob missing 0x prefix", () => { + test("Should reject preimage_blobs with blob missing 0x prefix", () => { const config = { services: [ { @@ -587,7 +587,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - preimages: { + preimage_blobs: { "0x0000000000000000000000000000000000000000000000000000000000000001": "deadbeef", }, }, @@ -598,7 +598,7 @@ describe("Validate Build Config", () => { expect(() => validateBuildConfig(config)).toThrow(); }); - test("Should reject preimages with blob consisting of an uneven number of characters", () => { + test("Should reject preimage_blobs with blob consisting of an uneven number of characters", () => { const config = { services: [ { @@ -611,7 +611,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - preimages: { + preimage_blobs: { "0x0000000000000000000000000000000000000000000000000000000000000001": "0xabc", }, }, @@ -622,7 +622,7 @@ describe("Validate Build Config", () => { expect(() => validateBuildConfig(config)).toThrow(); }); - test("Should reject preimages where hash is not 32 bytes long", () => { + test("Should reject preimage_blobs where hash is not 32 bytes long", () => { const config = { services: [ { @@ -635,7 +635,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - preimages: { + preimage_blobs: { "0x00000000000000000000000000000001": "0xdeadbeef", // 16 bytes instead of 32 }, }, @@ -647,8 +647,8 @@ describe("Validate Build Config", () => { }); }); - describe("Lookup History Config Validation", () => { - test("Should parse valid lookup_history config", () => { + describe("Preimage Requests Config Validation", () => { + test("Should parse valid preimage_requests config", () => { const config = { services: [ { @@ -661,7 +661,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - lookup_history: { + preimage_requests: { "0x0000000000000000000000000000000000000000000000000000000000000001": [100, 200, 300], "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": [42], }, @@ -671,13 +671,13 @@ describe("Validate Build Config", () => { }; const result = validateBuildConfig(config); - expect(result.deployment?.services?.["auth-service"]?.lookup_history).toEqual({ + expect(result.deployment?.services?.["auth-service"]?.preimageRequests).toEqual({ "0x0000000000000000000000000000000000000000000000000000000000000001": [100, 200, 300], "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890": [42], }); }); - test("Should accept lookup_history with empty array", () => { + test("Should accept preimage_requests with empty array", () => { const config = { services: [ { @@ -690,7 +690,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - lookup_history: { + preimage_requests: { "0x0000000000000000000000000000000000000000000000000000000000000001": [], }, }, @@ -699,12 +699,12 @@ describe("Validate Build Config", () => { }; const result = validateBuildConfig(config); - expect(result.deployment?.services?.["auth-service"]?.lookup_history).toEqual({ + expect(result.deployment?.services?.["auth-service"]?.preimageRequests).toEqual({ "0x0000000000000000000000000000000000000000000000000000000000000001": [], }); }); - test("Should accept lookup_history with 1, 2, and 3 time slots", () => { + test("Should accept preimage_requests with 1, 2, and 3 time slots", () => { const config = { services: [ { @@ -717,7 +717,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - lookup_history: { + preimage_requests: { "0x0000000000000000000000000000000000000000000000000000000000000001": [1], "0x0000000000000000000000000000000000000000000000000000000000000002": [1, 2], "0x0000000000000000000000000000000000000000000000000000000000000003": [1, 2, 3], @@ -728,13 +728,15 @@ describe("Validate Build Config", () => { }; const result = validateBuildConfig(config); - const lookupHistory = result.deployment?.services?.["auth-service"]?.lookup_history; - expect(lookupHistory?.["0x0000000000000000000000000000000000000000000000000000000000000001"]).toEqual([1]); - expect(lookupHistory?.["0x0000000000000000000000000000000000000000000000000000000000000002"]).toEqual([1, 2]); - expect(lookupHistory?.["0x0000000000000000000000000000000000000000000000000000000000000003"]).toEqual([1, 2, 3]); + const preimageRequests = result.deployment?.services?.["auth-service"]?.preimageRequests; + expect(preimageRequests?.["0x0000000000000000000000000000000000000000000000000000000000000001"]).toEqual([1]); + expect(preimageRequests?.["0x0000000000000000000000000000000000000000000000000000000000000002"]).toEqual([1, 2]); + expect(preimageRequests?.["0x0000000000000000000000000000000000000000000000000000000000000003"]).toEqual([ + 1, 2, 3, + ]); }); - test("Should reject lookup_history with more than 3 time slots", () => { + test("Should reject preimage_requests with more than 3 time slots", () => { const config = { services: [ { @@ -747,7 +749,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - lookup_history: { + preimage_requests: { "0x0000000000000000000000000000000000000000000000000000000000000001": [1, 2, 3, 4], }, }, @@ -758,7 +760,7 @@ describe("Validate Build Config", () => { expect(() => validateBuildConfig(config)).toThrow(); }); - test("Should reject lookup_history with non-integer time slot values", () => { + test("Should reject preimage_requests with non-integer time slot values", () => { const config = { services: [ { @@ -771,7 +773,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - lookup_history: { + preimage_requests: { "0x0000000000000000000000000000000000000000000000000000000000000001": [100.5, 200], }, }, @@ -782,7 +784,7 @@ describe("Validate Build Config", () => { expect(() => validateBuildConfig(config)).toThrow(); }); - test("Should accept lookup_history with negative time slot values", () => { + test("Should reject preimage_requests with negative time slot values", () => { const config = { services: [ { @@ -795,7 +797,7 @@ describe("Validate Build Config", () => { spawn: "local", services: { "auth-service": { - lookup_history: { + preimage_requests: { "0x0000000000000000000000000000000000000000000000000000000000000001": [-10, 0, 100], }, }, @@ -803,10 +805,7 @@ describe("Validate Build Config", () => { }, }; - const result = validateBuildConfig(config); - expect(result.deployment?.services?.["auth-service"]?.lookup_history).toEqual({ - "0x0000000000000000000000000000000000000000000000000000000000000001": [-10, 0, 100], - }); + expect(() => validateBuildConfig(config)).toThrow(); }); }); diff --git a/bin/cli/utils/config-validator.ts b/bin/cli/utils/config-validator.ts index 9d760ee..452b9e4 100644 --- a/bin/cli/utils/config-validator.ts +++ b/bin/cli/utils/config-validator.ts @@ -35,43 +35,52 @@ const ServiceConfigSchema = z.object({ ), }); -const ServiceDeploymentConfigSchema = z.object({ - id: u32Schema().optional(), - storage: z.record(z.string(), z.string()).optional(), - preimages: z.record(preimageHashSchema(), z.string().regex(/^0x([0-9a-fA-F]{2})+$/)).optional(), - lookup_history: z.record(preimageHashSchema(), z.array(z.int()).max(3)).optional(), - info: z - .object({ - balance: u64Schema().optional(), - accumulate_min_gas: u64Schema().optional(), - on_transfer_min_gas: u64Schema().optional(), - storage_utilisation_bytes: u64Schema().optional(), - gratis_storage: u64Schema().optional(), - storage_utilisation_count: u32Schema().optional(), - created: u32Schema().optional(), - last_accumulation: u32Schema().optional(), - parent_service: u32Schema().optional(), - }) - .transform((info) => { - // snake to camel case - if (!info) { - return undefined; - } - const transformed = { - balance: info.balance, - accumulateMinGas: info.accumulate_min_gas, - onTransferMinGas: info.on_transfer_min_gas, - storageUtilisationBytes: info.storage_utilisation_bytes, - gratisStorage: info.gratis_storage, - storageUtilisationCount: info.storage_utilisation_count, - created: info.created, - lastAccumulation: info.last_accumulation, - parentService: info.parent_service, - }; - return transformed; - }) - .optional(), -}); +const ServiceDeploymentConfigSchema = z + .object({ + id: u32Schema().optional(), + storage: z.record(z.string(), z.string()).optional(), + preimage_blobs: z.record(preimageHashSchema(), z.string().regex(/^0x([0-9a-fA-F]{2})+$/)).optional(), + preimage_requests: z.record(preimageHashSchema(), z.array(u32Schema()).max(3)).optional(), + info: z + .object({ + balance: u64Schema().optional(), + accumulate_min_gas: u64Schema().optional(), + on_transfer_min_gas: u64Schema().optional(), + storage_utilisation_bytes: u64Schema().optional(), + gratis_storage: u64Schema().optional(), + storage_utilisation_count: u32Schema().optional(), + created: u32Schema().optional(), + last_accumulation: u32Schema().optional(), + parent_service: u32Schema().optional(), + }) + .transform((info) => { + // snake to camel case + if (!info) { + return undefined; + } + const transformed = { + balance: info.balance, + accumulateMinGas: info.accumulate_min_gas, + onTransferMinGas: info.on_transfer_min_gas, + storageUtilisationBytes: info.storage_utilisation_bytes, + gratisStorage: info.gratis_storage, + storageUtilisationCount: info.storage_utilisation_count, + created: info.created, + lastAccumulation: info.last_accumulation, + parentService: info.parent_service, + }; + return transformed; + }) + .optional(), + }) + .transform((config) => { + const { preimage_blobs, preimage_requests, ...rest } = config; + return { + ...rest, + preimageBlobs: preimage_blobs, + preimageRequests: preimage_requests, + }; + }); const DeploymentConfigSchema = z.object({ spawn: z.string().min(1), diff --git a/packages/jammin-sdk/genesis-state-generator.test.ts b/packages/jammin-sdk/genesis-state-generator.test.ts index 86b4d1f..f0f80ee 100644 --- a/packages/jammin-sdk/genesis-state-generator.test.ts +++ b/packages/jammin-sdk/genesis-state-generator.test.ts @@ -147,16 +147,16 @@ describe("genesis-generator", () => { }); }); - describe("generateState with preimages and lookup history", () => { - test("should include additional preimages", async () => { + describe("generateState with preimage blobs and preimage requests", () => { + test("should include additional preimage blobs", async () => { const preimageBlob = BytesBlob.parseBlob("0xaabbccdd"); const preimageHash = blake2b.hashBytes(preimageBlob).asOpaque(); const serviceWithPreimages: ServiceBuildOutput = { ...basicService, id: ServiceId(300), - preimages: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), - lookupHistory: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0)])]]), + preimageBlobs: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), + preimageRequests: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0)])]]), }; const state = generateState([serviceWithPreimages]); @@ -166,15 +166,15 @@ describe("genesis-generator", () => { expect(serviceAccount).toBeDefined(); }); - test("should handle multiple slots in lookup history", async () => { + test("should handle multiple slots in preimage requests", async () => { const preimageBlob = BytesBlob.parseBlob("0x11223344"); const preimageHash = blake2b.hashBytes(preimageBlob).asOpaque(); const serviceWithMultiSlots: ServiceBuildOutput = { ...basicService, id: ServiceId(301), - preimages: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), - lookupHistory: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0), Slot(1), Slot(2)])]]), + preimageBlobs: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), + preimageRequests: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0), Slot(1), Slot(2)])]]), }; const state = generateState([serviceWithMultiSlots]); @@ -184,7 +184,7 @@ describe("genesis-generator", () => { expect(serviceAccount).toBeDefined(); }); - test("should throw error when preimage blob is missing for lookup history entry", async () => { + test("should throw error when preimage blob is missing for preimage request entry", async () => { const missingHash = Bytes.parseBytes( "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", HASH_SIZE, @@ -193,11 +193,33 @@ describe("genesis-generator", () => { const serviceWithMissingPreimage: ServiceBuildOutput = { ...basicService, id: ServiceId(302), - preimages: HashDictionary.fromEntries([]), // Empty - no preimage for the hash - lookupHistory: new Map([[missingHash, tryAsLookupHistorySlots([Slot(0)])]]), + preimageBlobs: HashDictionary.fromEntries([]), // Empty - no preimage for the hash + preimageRequests: new Map([[missingHash, tryAsLookupHistorySlots([Slot(0)])]]), }; expect(() => generateState([serviceWithMissingPreimage])).toThrow("Preimage blob not found for hash"); }); + + test("should calculate storage utilisation correctly with additional preimages", async () => { + const preimageBlob = BytesBlob.parseBlob("0xaabbccdd"); + const preimageHash = blake2b.hashBytes(preimageBlob).asOpaque(); + + const serviceWithPreimages: ServiceBuildOutput = { + ...basicService, + id: ServiceId(303), + preimageBlobs: HashDictionary.fromEntries([[preimageHash, preimageBlob]]), + preimageRequests: new Map([[preimageHash, tryAsLookupHistorySlots([Slot(0)])]]), + }; + + const state = generateState([serviceWithPreimages]); + + const service = state.services.get(ServiceId(303)); + expect(service).toBeDefined(); + + const info = service?.getInfo(); + + expect(info?.storageUtilisationCount).toBe(U32(4)); + expect(info?.storageUtilisationBytes).toEqual(U64(260)); + }); }); }); diff --git a/packages/jammin-sdk/genesis-state-generator.ts b/packages/jammin-sdk/genesis-state-generator.ts index 8493489..b46960b 100644 --- a/packages/jammin-sdk/genesis-state-generator.ts +++ b/packages/jammin-sdk/genesis-state-generator.ts @@ -32,6 +32,9 @@ export type Genesis = JipChainSpec; // https://graypaper.fluffylabs.dev/#/ab2cdbd/11e00111f001?v=0.7.2 const BASE_STORAGE_BYTES = 34n; +/** https://graypaper.fluffylabs.dev/#/ab2cdbd/11cc0111ce01?v=0.7.2 */ +const LOOKUP_HISTORY_ENTRY_BYTES = 81n; + // Base ServiceInfo const BASE_SERVICE: ServiceAccountInfo = { // actual codeHash of a given blob @@ -123,35 +126,20 @@ export function generateState(services: ServiceBuildOutput[]): InMemoryState { update.storage?.set(serviceId, storageUpdates); } - const calculatedStorageBytes = sumU64( + let calculatedStorageBytes = sumU64( BASE_SERVICE.storageUtilisationBytes, U64(service.code.length), U64(storageBytes), ).value; - const calculatedStorageCount = sumU32(BASE_SERVICE.storageUtilisationCount, U32(storageCount)).value; - - // create service - update.updated?.set( - serviceId, - UpdateService.create({ - serviceInfo: ServiceAccountInfo.create({ - ...BASE_SERVICE, - codeHash: codeHash.asOpaque(), - storageUtilisationBytes: calculatedStorageBytes, - storageUtilisationCount: calculatedStorageCount, - ...service.info, - }), - lookupHistory, - }), - ); + let calculatedStorageCount = sumU32(BASE_SERVICE.storageUtilisationCount, U32(storageCount)).value; - // add preimages and lookup history - if (service.lookupHistory) { + // add preimage blobs and preimage requests + if (service.preimageRequests) { const preimageUpdates = update.preimages?.get(serviceId) ?? []; - for (const [hash, slots] of service.lookupHistory) { - const preimageBlob = service.preimages?.get(hash); + for (const [hash, slots] of service.preimageRequests) { + const preimageBlob = service.preimageBlobs?.get(hash); if (!preimageBlob) { throw new Error(`Preimage blob not found for hash ${hash}`); @@ -161,6 +149,13 @@ export function generateState(services: ServiceBuildOutput[]): InMemoryState { const lookupHistory = new LookupHistoryItem(hash, U32(preimageBlob.length), tryAsLookupHistorySlots([])); preimageUpdates.push(UpdatePreimage.updateOrAdd({ lookupHistory })); + // update storage utilisation + calculatedStorageBytes = sumU64( + calculatedStorageBytes, + U64(LOOKUP_HISTORY_ENTRY_BYTES + BigInt(preimageBlob.length)), + ).value; + calculatedStorageCount = sumU32(calculatedStorageCount, U32(2)).value; + // provide if (slots.length >= 1) { if (preimageBlob) { @@ -185,6 +180,21 @@ export function generateState(services: ServiceBuildOutput[]): InMemoryState { update.preimages?.set(serviceId, preimageUpdates); } } + + // create service + update.updated?.set( + serviceId, + UpdateService.create({ + serviceInfo: ServiceAccountInfo.create({ + ...BASE_SERVICE, + codeHash: codeHash.asOpaque(), + storageUtilisationBytes: calculatedStorageBytes, + storageUtilisationCount: calculatedStorageCount, + ...service.info, + }), + lookupHistory, + }), + ); } memState.applyUpdate(update); diff --git a/packages/jammin-sdk/util/generate-service-output.ts b/packages/jammin-sdk/util/generate-service-output.ts index 90baeb5..fb1f838 100644 --- a/packages/jammin-sdk/util/generate-service-output.ts +++ b/packages/jammin-sdk/util/generate-service-output.ts @@ -18,8 +18,8 @@ export interface ServiceBuildOutput { code: BytesBlob; storage?: Record; info?: Partial; - preimages?: HashDictionary; - lookupHistory?: Map; + preimageBlobs?: HashDictionary; + preimageRequests?: Map; } export async function generateServiceOutput( @@ -37,8 +37,8 @@ export async function generateServiceOutput( lastAccumulation?: number; parentService?: number; }, - preimages?: Record, - lookupHistory?: Record, + preimageBlobs?: Record, + preimageRequests?: Record, ): Promise { const absolutePath = resolve(jamFilePath); const fileBytes = await Bun.file(absolutePath).bytes(); @@ -58,15 +58,15 @@ export async function generateServiceOutput( }).filter(([_, value]) => value !== undefined), ); - const preimagesDict = HashDictionary.fromEntries( - Object.entries(preimages ?? {}).map(([hash, blob]) => [ + const preimageBlobsDict = HashDictionary.fromEntries( + Object.entries(preimageBlobs ?? {}).map(([hash, blob]) => [ Bytes.parseBytes(hash, HASH_SIZE).asOpaque(), BytesBlob.blobFromString(blob), ]), ); - const lookupHistoryMap = new Map( - Object.entries(lookupHistory ?? {}).map(([hash, slots]) => [ + const preimageRequestsMap = new Map( + Object.entries(preimageRequests ?? {}).map(([hash, slots]) => [ Bytes.parseBytes(hash, HASH_SIZE).asOpaque(), tryAsLookupHistorySlots(slots.map((slot) => Slot(slot))), ]), @@ -77,7 +77,7 @@ export async function generateServiceOutput( code, storage, info: serviceAccountInfo, - preimages: preimagesDict, - lookupHistory: lookupHistoryMap, + preimageBlobs: preimageBlobsDict, + preimageRequests: preimageRequestsMap, }; } From bc92c426ec3f5385c812e633cc9cd69e816a04fa Mon Sep 17 00:00:00 2001 From: Sebastian Koszuta Date: Fri, 30 Jan 2026 12:24:13 +0100 Subject: [PATCH 5/5] fixed import path --- packages/jammin-sdk/genesis-state-generator.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jammin-sdk/genesis-state-generator.test.ts b/packages/jammin-sdk/genesis-state-generator.test.ts index f0f80ee..7dfc034 100644 --- a/packages/jammin-sdk/genesis-state-generator.test.ts +++ b/packages/jammin-sdk/genesis-state-generator.test.ts @@ -6,7 +6,7 @@ import { type StorageKey, tryAsLookupHistorySlots } from "@typeberry/lib/state"; import { asOpaqueType } from "@typeberry/lib/utils"; import { generateGenesis, generateState, toJip4Schema } from "./genesis-state-generator"; import { Gas, ServiceId, Slot, U32, U64 } from "./types"; -import type { ServiceBuildOutput } from "./util/generate-service-output"; +import type { ServiceBuildOutput } from "./utils/generate-service-output"; const blake2b = await Blake2b.createHasher();