diff --git a/bun.lock b/bun.lock index 360cc24..d94eb66 100644 --- a/bun.lock +++ b/bun.lock @@ -38,7 +38,7 @@ "name": "@fluffylabs/jammin-sdk", "version": "0.1.0", "dependencies": { - "@typeberry/lib": "0.5.2-9afefa7", + "@typeberry/lib": "0.5.4-e1cdb43", "yaml": "^2.8.2", "zod": "^4.3.6", }, @@ -84,7 +84,7 @@ "@tsconfig/node20": ["@tsconfig/node20@20.1.8", "", {}, "sha512-Em+IdPfByIzWRRpqWL4Z7ArLHZGxmc36BxE3jCz9nBFSm+5aLaPMZyjwu4yetvyKXeogWcxik4L1jB5JTWfw7A=="], - "@typeberry/lib": ["@typeberry/lib@0.5.2-9afefa7", "", { "dependencies": { "@fluffylabs/anan-as": "^1.1.5", "@noble/ed25519": "2.2.3", "@typeberry/native": "0.0.4-4c0cd28", "hash-wasm": "4.12.0" } }, "sha512-8RgtMK29flVgYRF8FEoTuwc249NLajBuQ7/fc+24bc8gNDkJH39mJiPz5TQKPcOPNQ6MSKnayMNO6u+i125RJA=="], + "@typeberry/lib": ["@typeberry/lib@0.5.4-e1cdb43", "", { "dependencies": { "@fluffylabs/anan-as": "^1.1.5", "@noble/ed25519": "2.2.3", "@typeberry/native": "0.0.4-4c0cd28", "hash-wasm": "4.12.0" } }, "sha512-gyUWD2j/SOFMnOTmfxIdlbRFdO5guwQrWB/2jBBf8rVu+Lys4M0jfqmralL16EIzbiapfwMbE+yrHfs8040ZkA=="], "@typeberry/native": ["@typeberry/native@0.0.4-4c0cd28", "", {}, "sha512-VhZiiSYex3/jDk1I8PlcwPxiM9GslryGxdG+4sbbjNvpr1JxRkH0fAdUnKzxAxGYPNz2MOfwHTmTdVcrk1a5rA=="], @@ -113,5 +113,9 @@ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "@fluffylabs/jammin-sdk/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], + + "@fluffylabs/jammin-sdk/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], } } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index afa6ce1..5b4f827 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -4,6 +4,7 @@ - [Requirements](requirements.md) - [Getting Started](getting-started.md) - [Service SDK examples](service-examples.md) +- [Testing JAM Services](testing.md) - [Contributing](contributing.md) - [Bootstrap](bootstrap/jammin-suite.md) - [Proposed API](bootstrap/jammin-examples.md) diff --git a/docs/src/testing.md b/docs/src/testing.md new file mode 100644 index 0000000..3351374 --- /dev/null +++ b/docs/src/testing.md @@ -0,0 +1,606 @@ +# Testing JAM Services + +This guide covers how to write integration tests for JAM services using the jammin SDK testing utilities. + +## Overview + +The jammin SDK provides a comprehensive testing framework for simulating JAM accumulation in your test environment. The core of this framework is the `TestJam` class, which provides a fluent API for creating work reports, running accumulation, and inspecting state changes. + +## Setup + +### Installation + +The testing utilities are included in the `@fluffylabs/jammin-sdk` package: + +```bash +bun add -d @fluffylabs/jammin-sdk +``` + +### Basic Test Structure + +Here's a minimal test using Bun's built-in test runner: + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, ServiceId, Gas } from "@fluffylabs/jammin-sdk"; + +test("should process work report", async () => { + // Create test instance with loaded services + const jam = await TestJam.create(); + + // Create a work report + const report = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + }); + + // Execute accumulation + const result = await jam.withWorkReport(report).accumulation(); + + // Verify results + expect(result).toBeDefined(); + expect(result.accumulationStatistics.size).toBe(1); +}); +``` + +## TestJam Class + +The `TestJam` class is the main entry point for testing JAM services. It manages state, work reports, and accumulation execution. + +### Creating a TestJam Instance + +#### With Loaded Services + +```typescript +// Loads all services from your project's jammin.build.yml files +const jam = await TestJam.create(); +``` + +This is the recommended approach for most tests as it automatically discovers and loads your service configurations. + +#### With Empty State + +```typescript +// Creates an empty state with no services +const jam = TestJam.empty(); +``` + +Useful for testing edge cases or when you don't need any services. + +### Adding Work Reports + +Work reports can be added using the fluent `withWorkReport()` API: + +```typescript +const report = await createWorkReportAsync({ + results: [ + { serviceId: ServiceId(0), gas: Gas(1000n) }, + { serviceId: ServiceId(1), gas: Gas(2000n) }, + ], +}); + +const result = await jam.withWorkReport(report).accumulation(); +``` + +#### Multiple Work Reports + +Chain multiple `withWorkReport()` calls to process multiple reports in a single accumulation: + +```typescript +const report1 = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], +}); + +const report2 = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(1), gas: Gas(2000n) }], +}); + +const result = await jam + .withWorkReport(report1) + .withWorkReport(report2) + .accumulation(); + +console.log(`Processed ${result.accumulationStatistics.size} work items`); +``` + +### Configuring Accumulation Options + +Use `withOptions()` to configure accumulation behavior: + +```typescript +import { PvmBackend } from "@fluffylabs/jammin-sdk/config"; + +const result = await jam + .withWorkReport(report) + .withOptions({ + slot: Slot(100), // Custom time slot + debug: true, // Enable debug logging + pvmBackend: PvmBackend.BuiltIn, // Use built-in PVM + sequential: true, // Sequential accumulation (default) + }) + .accumulation(); +``` + +#### Available Options + +- `slot?: TimeSlot` - Time slot for accumulation (defaults to state's current timeslot) +- `debug?: boolean` - Enable debug logging for accumulation and PVM host calls +- `pvmBackend?: PvmBackend` - PVM backend to use (`PvmBackend.Ananas` or `PvmBackend.BuiltIn`) +- `sequential?: boolean` - Use sequential accumulation mode (default: `true`) +- `entropy?: EntropyHash` - Entropy for randomness (defaults to zero hash for deterministic tests) +- `chainSpec?: ChainSpec` - Chain specification to use + +### Querying State + +After accumulation, you can query the service state: + +#### Get Service Info + +```typescript +const info = jam.getServiceInfo(ServiceId(0)); +console.log(`Balance: ${info?.balance}`); +console.log(`Gas used: ${info?.gasUsed}`); +``` + +#### Get Service Storage + +```typescript +import { BytesBlob } from "@fluffylabs/jammin-sdk/bytes"; + +const key = BytesBlob.blobFrom(new Uint8Array([1, 2, 3])); +const value = jam.getServiceStorage(ServiceId(0), key); +``` + +#### Get Preimage Data + +```typescript +const preimage = jam.getServicePreimage(ServiceId(0), someHash); +``` + +## Creating Work Reports + +The SDK provides flexible utilities for creating work reports with varying levels of detail. + +### Simple Work Report + +```typescript +import { createWorkReportAsync, ServiceId, Gas } from "@fluffylabs/jammin-sdk"; + +const report = await createWorkReportAsync({ + results: [ + { serviceId: ServiceId(0), gas: Gas(1000n) }, + ], +}); +``` + +### Work Report with Multiple Work Items + +```typescript +const report = await createWorkReportAsync({ + results: [ + { + serviceId: ServiceId(0), + gas: Gas(1000n), + result: { type: "ok", output: BytesBlob.blobFrom(new Uint8Array([1, 2, 3])) } + }, + { + serviceId: ServiceId(1), + gas: Gas(2000n), + result: { type: "ok" } + }, + { + serviceId: ServiceId(2), + gas: Gas(500n), + result: { type: "panic" } // Simulate a panic + }, + ], +}); +``` + +### Work Report with Custom Configuration + +```typescript +import { CoreId, Slot } from "@fluffylabs/jammin-sdk"; + +const report = await createWorkReportAsync({ + coreIndex: CoreId(5), + results: [ + { + serviceId: ServiceId(0), + gas: Gas(1000n), + payload: BytesBlob.blobFrom(new Uint8Array([4, 5, 6])), + }, + ], + context: { + lookupAnchorSlot: Slot(42), + }, +}); +``` + +### Work Result Types + +Work results can have different status types: + +```typescript +// Successful execution +{ type: "ok", output: BytesBlob.blobFrom(...) } + +// Execution errors +{ type: "panic" } +{ type: "outOfGas" } +{ type: "badCode" } +{ type: "digestTooBig" } +{ type: "incorrectNumberOfExports" } +{ type: "codeOversize" } +``` + +## Testing Helpers + +The SDK provides assertion helpers to simplify test writing. + +### expectAccumulationSuccess + +Assert that accumulation completed with a valid structure: + +```typescript +import { expectAccumulationSuccess } from "@fluffylabs/jammin-sdk/testing-helpers"; + +const result = await jam.withWorkReport(report).accumulation(); +expectAccumulationSuccess(result); // Throws if result is invalid +``` + +### expectWorkItemCount + +Assert that the expected number of work items were processed: + +```typescript +import { expectWorkItemCount } from "@fluffylabs/jammin-sdk/testing-helpers"; + +const result = await jam + .withWorkReport(report1) + .withWorkReport(report2) + .accumulation(); + +expectWorkItemCount(result, 2); // Throws if count doesn't match +``` + +### expectStateChange + +Assert that state changed according to a predicate: + +```typescript +import { expectStateChange } from "@fluffylabs/jammin-sdk/testing-helpers"; + +const beforeBalance = jam.getServiceInfo(ServiceId(0))?.balance; + +await jam.withWorkReport(report).accumulation(); + +const afterBalance = jam.getServiceInfo(ServiceId(0))?.balance; + +expectStateChange( + beforeBalance, + afterBalance, + (before, after) => after !== undefined && before !== undefined && after > before, + "Balance should increase" +); +``` + +### expectServiceInfoChange + +Specialized helper for validating service account changes: + +```typescript +import { expectServiceInfoChange } from "@fluffylabs/jammin-sdk/testing-helpers"; + +const before = jam.getServiceInfo(ServiceId(0)); +await jam.withWorkReport(report).accumulation(); +const after = jam.getServiceInfo(ServiceId(0)); + +expectServiceInfoChange( + before, + after, + (b, a) => a && b && a.gasUsed > b.gasUsed, + "Service should consume gas" +); +``` + +## Common Test Patterns + +### Testing Service Execution + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, ServiceId, Gas } from "@fluffylabs/jammin-sdk"; +import { expectAccumulationSuccess } from "@fluffylabs/jammin-sdk/testing-helpers"; + +test("service should execute successfully", async () => { + const jam = await TestJam.create(); + + const report = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(100000n) }], + }); + + const result = await jam.withWorkReport(report).accumulation(); + + expectAccumulationSuccess(result); + expect(result.accumulationStatistics.size).toBe(1); +}); +``` + +### Testing State Changes + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, ServiceId, Gas, BytesBlob } from "@fluffylabs/jammin-sdk"; + +test("service storage should update", async () => { + const jam = await TestJam.create(); + + const storageKey = BytesBlob.blobFromString("myKey"); + const beforeValue = jam.getServiceStorage(ServiceId(0), storageKey); + + const report = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(50000n) }], + }); + + await jam.withWorkReport(report).accumulation(); + + const afterValue = jam.getServiceStorage(ServiceId(0), storageKey); + expect(afterValue).not.toEqual(beforeValue); +}); +``` + +### Testing Multiple Services + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, ServiceId, Gas } from "@fluffylabs/jammin-sdk"; + +test("should process multiple services", async () => { + const jam = await TestJam.create(); + + const report = await createWorkReportAsync({ + results: [ + { serviceId: ServiceId(0), gas: Gas(10000n) }, + { serviceId: ServiceId(1), gas: Gas(20000n) }, + { serviceId: ServiceId(2), gas: Gas(15000n) }, + ], + }); + + const result = await jam.withWorkReport(report).accumulation(); + + expect(result.accumulationStatistics.size).toBe(3); +}); +``` + +### Testing Error Conditions + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, ServiceId, Gas } from "@fluffylabs/jammin-sdk"; + +test("should handle panic gracefully", async () => { + const jam = await TestJam.create(); + + const report = await createWorkReportAsync({ + results: [ + { + serviceId: ServiceId(0), + gas: Gas(1000n), + result: { type: "panic" } + }, + ], + }); + + const result = await jam.withWorkReport(report).accumulation(); + + // Accumulation should complete even with panicked work items + expect(result).toBeDefined(); + expect(result.accumulationStatistics.size).toBe(1); +}); +``` + +### Testing Sequential Accumulations + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, ServiceId, Gas } from "@fluffylabs/jammin-sdk"; + +test("state should persist across accumulations", async () => { + const jam = await TestJam.create(); + + // First accumulation + const report1 = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + }); + await jam.withWorkReport(report1).accumulation(); + + const midInfo = jam.getServiceInfo(ServiceId(0)); + + // Second accumulation + const report2 = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(2000n) }], + }); + await jam.withWorkReport(report2).accumulation(); + + const finalInfo = jam.getServiceInfo(ServiceId(0)); + + // State should have accumulated from both operations + expect(finalInfo).toBeDefined(); + expect(midInfo).toBeDefined(); +}); +``` + +### Testing with Guarantees + +```typescript +import { test, expect } from "bun:test"; +import { TestJam, createWorkReportAsync, generateGuarantees, ServiceId, Gas, Slot } from "@fluffylabs/jammin-sdk"; + +test("should generate valid guarantees", async () => { + const jam = await TestJam.create(); + + const report = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + }); + + // Generate guarantees with validator signatures + const guarantees = await generateGuarantees([report], { + slot: Slot(100), + }); + + expect(guarantees).toHaveLength(1); + expect(guarantees[0]?.credentials.length).toBe(3); // Default 3 validators + expect(Number(guarantees[0]?.slot)).toBe(100); +}); +``` + +## Troubleshooting + +### Service Not Found + +If you see errors like "Service with id X not found", ensure that: + +1. Your `jammin.build.yml` file is properly configured +2. You're using `TestJam.create()` (not `TestJam.empty()`) +3. The service ID in your test matches the service ID in your configuration + +### Import Errors + +Make sure you're importing from the correct module: + +```typescript +// Correct +import { TestJam, createWorkReportAsync } from "@fluffylabs/jammin-sdk"; +import { expectAccumulationSuccess } from "@fluffylabs/jammin-sdk/testing-helpers"; + +// Also correct (testing-helpers re-exports everything) +import { + TestJam, + createWorkReportAsync, + expectAccumulationSuccess +} from "@fluffylabs/jammin-sdk/testing-helpers"; +``` + +### Type Errors with Branded Types + +The SDK uses branded types for safety. Use the helper functions to create them: + +```typescript +import { ServiceId, Gas, CoreId, Slot, U32 } from "@fluffylabs/jammin-sdk"; + +// Correct +const serviceId = ServiceId(0); +const gas = Gas(1000n); +const coreId = CoreId(5); +const slot = Slot(100); +const value = U32(42); + +// Incorrect - will cause type errors +const serviceId = 0; // Type error +const gas = 1000n; // Type error +``` + +### Debug Logging + +Enable debug logging to see what's happening during accumulation: + +```typescript +const result = await jam + .withWorkReport(report) + .withOptions({ debug: true }) + .accumulation(); +``` + +This will output detailed logs including: +- Accumulation steps +- PVM host calls + +### State Not Persisting + +Remember that `accumulation()` automatically applies state updates. If you need to inspect state at different points: + +```typescript +// Check initial state +const initialInfo = jam.getServiceInfo(ServiceId(0)); + +// Run first accumulation (state is updated) +await jam.withWorkReport(report1).accumulation(); + +// Check intermediate state +const midInfo = jam.getServiceInfo(ServiceId(0)); + +// Run second accumulation (state is updated again) +await jam.withWorkReport(report2).accumulation(); + +// Check final state +const finalInfo = jam.getServiceInfo(ServiceId(0)); +``` + +## Advanced Usage + +### Custom Blake2b Hasher + +For more control over work report creation: + +```typescript +import { Blake2b } from "@fluffylabs/jammin-sdk/hash"; +import { createWorkReport } from "@fluffylabs/jammin-sdk"; + +const blake2b = await Blake2b.createHasher(); + +const report = createWorkReport(blake2b, { + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], +}); +``` + +### Manual State Management + +For advanced use cases, you can manually manage state: + +```typescript +import { generateState, loadServices } from "@fluffylabs/jammin-sdk"; + +const services = await loadServices(); +const state = generateState(services); + +// Use state directly with simulateAccumulation +import { simulateAccumulation } from "@fluffylabs/jammin-sdk"; + +const result = await simulateAccumulation(state, [report], { + debug: true, +}); +``` + +### Custom Chain Specifications + +Override the default chain spec: + +```typescript +import { tinyChainSpec } from "@fluffylabs/jammin-sdk/config"; + +const customSpec = { + ...tinyChainSpec, + // Customize as needed +}; + +const result = await jam + .withWorkReport(report) + .withOptions({ chainSpec: customSpec }) + .accumulation(); +``` + +## Best Practices + +1. **Use `TestJam.create()` by default** - It automatically loads your services +2. **Chain method calls** - The fluent API makes tests more readable +3. **Use helper assertions** - They provide better error messages than raw `expect()` +4. **Test state changes explicitly** - Don't assume accumulation modified state +5. **Use branded types** - They prevent common mistakes with raw numbers +6. **Enable debug logging when troubleshooting** - It shows exactly what's happening +7. **Test both success and failure cases** - Include tests for panics and out-of-gas scenarios +8. **Keep tests isolated** - Create a new `TestJam` instance for each test + +## Next Steps + +- Review the [Service SDK examples](service-examples.md) for service implementation patterns +- Explore the [jammin suite](bootstrap/jammin-suite.md) for more tools and features diff --git a/packages/jammin-sdk/index.ts b/packages/jammin-sdk/index.ts index 0057430..f4ecf12 100644 --- a/packages/jammin-sdk/index.ts +++ b/packages/jammin-sdk/index.ts @@ -5,14 +5,12 @@ export * as config from "@typeberry/lib/config"; export * as config_node from "@typeberry/lib/config-node"; export * as hash from "@typeberry/lib/hash"; export * as numbers from "@typeberry/lib/numbers"; -export * as state from "@typeberry/lib/state"; export * as state_merkleization from "@typeberry/lib/state-merkleization"; export * from "./config/index.js"; export * from "./genesis-state-generator.js"; -export * from "./simulator.js"; +export * from "./testing-helpers.js"; export * from "./types.js"; export * from "./utils/index.js"; -export * from "./work-report.js"; export function getSDKInfo() { return { diff --git a/packages/jammin-sdk/package.json b/packages/jammin-sdk/package.json index a3bc944..3c774b1 100644 --- a/packages/jammin-sdk/package.json +++ b/packages/jammin-sdk/package.json @@ -30,7 +30,7 @@ "type": "module", "types": "./dist/index.d.ts", "dependencies": { - "@typeberry/lib": "0.5.2-9afefa7", + "@typeberry/lib": "0.5.4-e1cdb43", "yaml": "^2.8.2", "zod": "^4.3.6" } diff --git a/packages/jammin-sdk/simulator.test.ts b/packages/jammin-sdk/simulator.test.ts index 687a410..58beb72 100644 --- a/packages/jammin-sdk/simulator.test.ts +++ b/packages/jammin-sdk/simulator.test.ts @@ -1,16 +1,14 @@ import { beforeAll, describe, expect, test } from "bun:test"; import * as config from "@typeberry/lib/config"; -import type * as state from "@typeberry/lib/state"; -import { generateState } from "./genesis-state-generator.js"; -import { generateGuarantees, simulateAccumulation } from "./simulator.js"; +import { generateGuarantees, TestJam } from "./simulator.js"; import { CoreId, Gas, ServiceId, Slot } from "./types.js"; import { createWorkReportAsync } from "./work-report.js"; describe("simulateAccumulation", () => { - let initialState: state.InMemoryState; + let jam: TestJam; - beforeAll(async () => { - initialState = generateState([]); + beforeAll(() => { + jam = TestJam.empty(); }); test("simulates accumulation with minimal configuration", async () => { @@ -18,12 +16,11 @@ describe("simulateAccumulation", () => { results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], }); - const result = await simulateAccumulation(initialState, [report]); + const result = await jam.withWorkReport(report).accumulation(); expect(result).toBeDefined(); expect(result.stateUpdate).toBeDefined(); expect(result.accumulationStatistics).toBeDefined(); - expect(result.pendingTransfers).toBeDefined(); expect(result.accumulationOutputLog).toBeDefined(); expect(result.accumulationStatistics.size).toBe(1); }); @@ -36,7 +33,7 @@ describe("simulateAccumulation", () => { results: [{ serviceId: ServiceId(1), gas: Gas(750n) }], }); - const result = await simulateAccumulation(initialState, [report1, report2]); + const result = await jam.withWorkReport(report1).withWorkReport(report2).accumulation(); expect(result.accumulationStatistics).toBeDefined(); expect(result.accumulationStatistics.size).toBe(2); @@ -51,16 +48,14 @@ describe("simulateAccumulation", () => { ], }); - const result = await simulateAccumulation(initialState, [report], { - chainSpec: config.tinyChainSpec, - }); + const result = await jam.withWorkReport(report).accumulation(); expect(result).toBeDefined(); expect(result.accumulationStatistics.size).toBe(3); }); test("handles empty reports array", async () => { - const result = await simulateAccumulation(initialState, []); + const result = await jam.accumulation(); expect(result).toBeDefined(); expect(result.stateUpdate).toBeDefined(); @@ -73,9 +68,12 @@ describe("simulateAccumulation", () => { }); const customSlot = Slot(100); - const result = await simulateAccumulation(initialState, [report], { - slot: customSlot, - }); + const result = await jam + .withWorkReport(report) + .withOptions({ + slot: customSlot, + }) + .accumulation(); expect(result.stateUpdate.timeslot).toBeDefined(); // The timeslot in the update should be >= the slot we passed @@ -87,9 +85,12 @@ describe("simulateAccumulation", () => { results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], }); - const result = await simulateAccumulation(initialState, [report], { - pvmBackend: config.PvmBackend.BuiltIn, - }); + const result = await jam + .withWorkReport(report) + .withOptions({ + pvmBackend: config.PvmBackend.BuiltIn, + }) + .accumulation(); expect(result).toBeDefined(); }); diff --git a/packages/jammin-sdk/simulator.ts b/packages/jammin-sdk/simulator.ts index 5a9d9f3..b02892a 100644 --- a/packages/jammin-sdk/simulator.ts +++ b/packages/jammin-sdk/simulator.ts @@ -1,19 +1,24 @@ -import type { EntropyHash, TimeSlot } from "@typeberry/lib/block"; import * as jamBlock from "@typeberry/lib/block"; +import { Credential, type EntropyHash, ReportGuarantee, type TimeSlot } from "@typeberry/lib/block"; +import type { BytesBlob } from "@typeberry/lib/bytes"; import { Encoder } from "@typeberry/lib/codec"; import { asKnownSize } from "@typeberry/lib/collections"; import { type ChainSpec, PvmBackend, tinyChainSpec } from "@typeberry/lib/config"; import { ed25519, keyDerivation } from "@typeberry/lib/crypto"; -import { Blake2b, ZERO_HASH } from "@typeberry/lib/hash"; +import { Blake2b, type OpaqueHash, ZERO_HASH } from "@typeberry/lib/hash"; import * as jamNumbers from "@typeberry/lib/numbers"; -import type { State } from "@typeberry/lib/state"; +import { InMemoryState, type LookupHistorySlots, type ServiceAccountInfo, type State } from "@typeberry/lib/state"; +import { type SerializedState, type StateEntries, serializeStateUpdate } from "@typeberry/lib/state-merkleization"; import { Accumulate, type AccumulateInput, type AccumulateResult, type AccumulateState, } from "@typeberry/lib/transition"; +import { asOpaqueType } from "@typeberry/lib/utils"; +import { generateState } from "./genesis-state-generator.js"; import { Slot } from "./types.js"; +import { loadServices } from "./utils/generate-service-output.js"; import type { WorkReport } from "./work-report.js"; // Re-export types for convenience @@ -88,7 +93,7 @@ async function signWorkReport( keyPair: Awaited, blake2b: Blake2b, ): Promise { - const reportBlob = Encoder.encodeObject(jamBlock.workReport.WorkReport.Codec, workReport); + const reportBlob = Encoder.encodeObject(jamBlock.WorkReport.Codec, workReport); const reportHash = blake2b.hashBytes(reportBlob); @@ -116,17 +121,17 @@ async function signWorkReport( export async function generateGuarantees( workReports: WorkReport[], options: GuaranteeOptions = {}, -): Promise { +): Promise { const slot = options.slot ?? Slot(0); const credentialCount = 3; const startValidatorIndex = 0; const blake2b = await Blake2b.createHasher(); - const guarantees: jamBlock.guarantees.ReportGuarantee[] = []; + const guarantees: ReportGuarantee[] = []; for (const workReport of workReports) { - const credentials: jamBlock.guarantees.Credential[] = []; + const credentials: Credential[] = []; for (let i = 0; i < credentialCount; i++) { const validatorIndexNum = startValidatorIndex + i; @@ -135,7 +140,7 @@ export async function generateGuarantees( const signature = await signWorkReport(workReport, keyPair, blake2b); credentials.push( - jamBlock.guarantees.Credential.create({ + Credential.create({ validatorIndex, signature, }), @@ -143,7 +148,7 @@ export async function generateGuarantees( } guarantees.push( - jamBlock.guarantees.ReportGuarantee.create({ + ReportGuarantee.create({ report: workReport, slot, credentials: asKnownSize(credentials), @@ -205,3 +210,220 @@ async function enableLogs() { console.warn("Warning: Could not configure typeberry logger"); } } + +/** + * Test helper class for simulating JAM accumulation in Bun tests. + * Automatically generates state on construction and provides a fluent API + * for injecting work reports and running accumulation. + * + * @example + * ```typescript + * // Create a test instance with loaded services + * const jam = await TestJam.create(); + * + * // Create and submit a work report + * const report = await createWorkReportAsync({ + * results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + * }); + * const result = await jam.withWorkReport(report).accumulation(); + * + * // Chain multiple work reports + * const result2 = await jam + * .withWorkReport(report1) + * .withWorkReport(report2) + * .withOptions({ debug: true }) + * .accumulation(); + * + * // Query service state after accumulation + * const serviceInfo = jam.getServiceInfo(ServiceId(0)); + * ``` + */ +export class TestJam { + public readonly state: InMemoryState | SerializedState; + private workReports: WorkReport[] = []; + private options: SimulatorOptions = {}; + private blake2b?: Blake2b; + + private constructor(state: InMemoryState | SerializedState) { + this.state = state; + } + + /** + * Create a new TestJam instance with services loaded from the project. + * This is the recommended way to initialize TestJam for most tests. + * + * @returns Promise resolving to a new TestJam instance + * + * @example + * ```typescript + * const jam = await TestJam.create(); + * ``` + */ + static async create(): Promise { + const state = generateState(await loadServices()); + return new TestJam(state); + } + + /** + * Create a new TestJam instance with empty state (no services). + * Useful for testing edge cases or when services are not needed. + * + * @returns A new TestJam instance with empty state + * + * @example + * ```typescript + * const jam = TestJam.empty(); + * ``` + */ + static empty(): TestJam { + const state = generateState([]); + return new TestJam(state); + } + + /** + * Configure simulator options for the next accumulation. + * Options persist across multiple accumulation() calls until changed. + * + * @param options - Simulator configuration options + * @returns This instance for method chaining + * + * @example + * ```typescript + * const result = await jam + * .withOptions({ debug: true, slot: Slot(100) }) + * .withWorkReport(report) + * .accumulation(); + * ``` + */ + withOptions(options: SimulatorOptions): this { + this.options = options; + return this; + } + + /** + * Add a work report to be processed in the next accumulation. + * Multiple work reports can be chained before calling accumulation(). + * + * @param report - Work report to process + * @returns This instance for method chaining + * + * @example + * ```typescript + * const report1 = await createWorkReportAsync({ + * results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + * }); + * const report2 = await createWorkReportAsync({ + * results: [{ serviceId: ServiceId(1), gas: Gas(2000n) }], + * }); + * + * const result = await jam + * .withWorkReport(report1) + * .withWorkReport(report2) + * .accumulation(); + * ``` + */ + withWorkReport(report: WorkReport): this { + this.workReports.push(report); + return this; + } + + /** + * Execute accumulation with all queued work reports and apply state changes. + * Work reports are automatically cleared after accumulation completes. + * + * @returns Promise resolving to accumulation result including state updates + * @throws Error if accumulation fails + * + * @example + * ```typescript + * const result = await jam.withWorkReport(report).accumulation(); + * console.log(`Processed ${result.accumulationStatistics.size} work items`); + * ``` + */ + async accumulation(): Promise { + const result = await simulateAccumulation(this.state, this.workReports, this.options); + this.workReports = []; + if (this.state instanceof InMemoryState) { + this.state.applyUpdate(result); + } else { + if (!this.blake2b) { + this.blake2b = await Blake2b.createHasher(); + } + if (!this.options.chainSpec) { + this.options.chainSpec = tinyChainSpec; + } + this.state.backend.applyUpdate(serializeStateUpdate(this.options.chainSpec, this.blake2b, result)); + } + return result; + } + + /** + * Get service account information for a specific service ID. + * + * @param id - Service ID to query + * @returns Service account info or undefined if not found + * + * @example + * ```typescript + * const info = jam.getServiceInfo(ServiceId(0)); + * console.log(`Service balance: ${info?.balance}`); + * ``` + */ + getServiceInfo(id: jamBlock.ServiceId): ServiceAccountInfo | undefined { + return this.state.getService(id)?.getInfo(); + } + + /** + * Get storage value for a specific key in a service's storage. + * + * @param id - Service ID + * @param key - Storage key + * @returns Storage value, undefined if service not found, null if key not found + * + * @example + * ```typescript + * const key = BytesBlob.blobFrom(new Uint8Array([1, 2, 3])); + * const value = jam.getServiceStorage(ServiceId(0), key); + * ``` + */ + getServiceStorage(id: jamBlock.ServiceId, key: BytesBlob): BytesBlob | undefined | null { + return this.state.getService(id)?.getStorage(asOpaqueType(key)); + } + + /** + * Get preimage data for a given hash in a service's preimage store. + * + * @param id - Service ID + * @param hash - Preimage hash + * @returns Preimage data, undefined if service not found, null if preimage not found + * + * @example + * ```typescript + * const preimage = jam.getServicePreimage(ServiceId(0), someHash); + * ``` + */ + getServicePreimage(id: jamBlock.ServiceId, hash: OpaqueHash): BytesBlob | undefined | null { + return this.state.getService(id)?.getPreimage(hash.asOpaque()); + } + + /** + * Get preimage lookup history for a given hash in a service's preimage store. + * + * @param id - Service ID + * @param hash - Preimage hash + * @param len - Length parameter + * @returns Lookup history, undefined if service not found, null if not found + * + * @example + * ```typescript + * const history = jam.getServicePreimageLookup(ServiceId(0), someHash, U32(32)); + * ``` + */ + getServicePreimageLookup( + id: jamBlock.ServiceId, + hash: OpaqueHash, + len: jamNumbers.U32, + ): LookupHistorySlots | undefined | null { + return this.state.getService(id)?.getLookupHistory(hash.asOpaque(), len); + } +} diff --git a/packages/jammin-sdk/testing-helpers.test.ts b/packages/jammin-sdk/testing-helpers.test.ts new file mode 100644 index 0000000..b840525 --- /dev/null +++ b/packages/jammin-sdk/testing-helpers.test.ts @@ -0,0 +1,107 @@ +import { beforeAll, describe, expect, test } from "bun:test"; +import { ZERO_HASH } from "@typeberry/lib/hash"; +import { ServiceAccountInfo } from "@typeberry/lib/state"; +import { + expectAccumulationSuccess, + expectServiceInfoChange, + expectStateChange, + generateGuarantees, + StateChangeAssertionError, + TestJam, +} from "./testing-helpers.js"; +import { CoreId, Gas, ServiceId, Slot, U32, U64 } from "./types.js"; +import { createWorkReportAsync } from "./work-report.js"; + +describe("testing-helpers", () => { + let jam: TestJam; + + beforeAll(() => { + jam = TestJam.empty(); + }); + + describe("generateGuarantees re-export", () => { + test("should generate guarantees from testing-helpers", async () => { + const report = await createWorkReportAsync({ + coreIndex: CoreId(0), + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + }); + + const guarantees = await generateGuarantees([report], { + slot: Slot(42), + }); + + expect(guarantees).toHaveLength(1); + expect(guarantees[0]?.credentials.length).toBe(3); + }); + }); + + describe("expectAccumulationSuccess", () => { + test("should pass for valid accumulation result", async () => { + const report = await createWorkReportAsync({ + results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + }); + + const result = await jam.withWorkReport(report).accumulation(); + + // Should not throw + expectAccumulationSuccess(result); + }); + }); + + describe("expectStateChange", () => { + test("should pass when predicate returns true", () => { + expectStateChange(10, 20, (before, after) => after > before); + }); + + test("should pass when predicate returns undefined (no explicit return)", () => { + // If predicate doesn't return false, it passes + expectStateChange(10, 20, (_before, _after) => undefined); + }); + + test("should throw when predicate returns false", () => { + expect(() => { + expectStateChange(10, 5, (before, after) => after > before, "Should increase"); + }).toThrow(StateChangeAssertionError); + }); + + test("should include custom error message", () => { + try { + expectStateChange(10, 5, (before, after) => after > before, "Custom error"); + } catch (e) { + expect(e).toBeInstanceOf(StateChangeAssertionError); + expect((e as Error).message).toBe("Custom error"); + } + }); + }); + + describe("expectServiceInfoChange", () => { + const zeroService = ServiceAccountInfo.create({ + accumulateMinGas: Gas(0), + balance: U64(0), + codeHash: ZERO_HASH.asOpaque(), + created: Slot(0), + gratisStorage: U64(0), + lastAccumulation: Slot(0), + onTransferMinGas: Gas(0), + parentService: ServiceId(0), + storageUtilisationBytes: U64(0), + storageUtilisationCount: U32(0), + }); + + test("should pass when service info predicate is satisfied", () => { + const before = ServiceAccountInfo.create({ ...zeroService, balance: U64(1000) }); + const after = ServiceAccountInfo.create({ ...zeroService, balance: U64(1500) }); + + expectServiceInfoChange(before, after, (b, a) => a && b && a.balance > b.balance, "Balance should increase"); + }); + + test("should throw when predicate fails", () => { + const before = ServiceAccountInfo.create({ ...zeroService, balance: U64(1000) }); + const after = ServiceAccountInfo.create({ ...zeroService, balance: U64(500) }); + + expect(() => { + expectServiceInfoChange(before, after, (b, a) => a && b && a.balance > b.balance, "Balance should increase"); + }).toThrow(StateChangeAssertionError); + }); + }); +}); diff --git a/packages/jammin-sdk/testing-helpers.ts b/packages/jammin-sdk/testing-helpers.ts new file mode 100644 index 0000000..1da2f7d --- /dev/null +++ b/packages/jammin-sdk/testing-helpers.ts @@ -0,0 +1,170 @@ +/** + * Testing helpers for JAM service development and integration tests. + * Provides utilities for assertions, state comparisons, and common test patterns. + * + * @module testing-helpers + */ + +import type { ServiceAccountInfo } from "@typeberry/lib/state"; +import type { AccumulateResult } from "@typeberry/lib/transition"; + +export type { AccumulateResult, AccumulateState, GuaranteeOptions, SimulatorOptions, State } from "./simulator.js"; +export { generateGuarantees, simulateAccumulation, TestJam } from "./simulator.js"; +export type { WorkReport, WorkReportConfig, WorkResultConfig, WorkResultStatus } from "./work-report.js"; +export { createWorkReport, createWorkReportAsync, createWorkResult } from "./work-report.js"; + +/** + * Custom error thrown when accumulation assertions fail + */ +export class AccumulationAssertionError extends Error { + constructor( + message: string, + public result: AccumulateResult, + ) { + super(message); + this.name = "AccumulationAssertionError"; + } +} + +/** + * Custom error thrown when state change assertions fail + */ +export class StateChangeAssertionError extends Error { + constructor( + message: string, + public before: unknown, + public after: unknown, + ) { + super(message); + this.name = "StateChangeAssertionError"; + } +} + +/** + * Assert that accumulation completed successfully without errors. + * Validates that the accumulation result has the expected structure. + * + * @param result - Accumulation result to validate + * @throws AccumulationAssertionError if accumulation structure is invalid + * + * @example + * ```typescript + * import { expectAccumulationSuccess } from "@fluffylabs/jammin-sdk/testing-helpers"; + * + * const jam = await TestJam.create(); + * const report = await createWorkReportAsync({ + * results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + * }); + * const result = await jam.withWorkReport(report).accumulation(); + * + * // Assert accumulation completed + * expectAccumulationSuccess(result); + * ``` + */ +export function expectAccumulationSuccess(result: AccumulateResult): void { + if (!result.stateUpdate) { + throw new AccumulationAssertionError("Accumulation result missing stateUpdate", result); + } + + if (!result.accumulationStatistics) { + throw new AccumulationAssertionError("Accumulation result missing accumulationStatistics", result); + } + + if (!result.accumulationOutputLog) { + throw new AccumulationAssertionError("Accumulation result missing accumulationOutputLog", result); + } +} + +/** + * Predicate function for validating state changes. + * Returns true if the state change is valid, false or throws an error otherwise. + */ +export type StateChangePredicate = (before: T, after: T) => boolean | undefined; + +/** + * Assert that a state change satisfies a given predicate. + * Useful for validating service state transitions after accumulation. + * + * @param before - State before accumulation + * @param after - State after accumulation + * @param predicate - Function that validates the state change + * @param errorMessage - Custom error message if assertion fails + * @throws StateChangeAssertionError if predicate returns false + * + * @example + * ```typescript + * import { expectStateChange } from "@fluffylabs/jammin-sdk/testing-helpers"; + * + * const jam = await TestJam.create(); + * const beforeInfo = jam.getServiceInfo(ServiceId(0)); + * + * const report = await createWorkReportAsync({ + * results: [{ serviceId: ServiceId(0), gas: Gas(1000n) }], + * }); + * await jam.withWorkReport(report).accumulation(); + * + * const afterInfo = jam.getServiceInfo(ServiceId(0)); + * + * // Assert that balance increased + * expectStateChange( + * beforeInfo?.balance, + * afterInfo?.balance, + * (before, after) => after > before, + * "Service balance should increase after work" + * ); + * ``` + */ +export function expectStateChange( + before: T, + after: T, + predicate: StateChangePredicate, + errorMessage?: string, +): void { + const result = predicate(before, after); + + // If predicate returns false explicitly, throw error + if (result === false) { + throw new StateChangeAssertionError(errorMessage ?? "State change validation failed", before, after); + } +} + +/** + * Assert that service account information changed as expected. + * Common use case for validating balance changes, gas refunds, etc. + * + * @param before - Service info before accumulation + * @param after - Service info after accumulation + * @param predicate - Function that validates the info change + * @param errorMessage - Custom error message if assertion fails + * @throws StateChangeAssertionError if predicate returns false + * + * @example + * ```typescript + * import { expectServiceInfoChange } from "@fluffylabs/jammin-sdk/testing-helpers"; + * + * const jam = await TestJam.create(); + * const before = jam.getServiceInfo(ServiceId(0)); + * + * await jam.withWorkReport(report).withOptions({ slot: Slot(100) }).accumulation(); + * + * const after = jam.getServiceInfo(ServiceId(0)); + * + * // Validate that gas was consumed + * expectServiceInfoChange( + * before, + * after, + * (b, a) => { + * return a && b && a.lastAccumulation > b.lastAccumulation; + * }, + * "Service should update last accumulation during execution" + * ); + * ``` + */ +export function expectServiceInfoChange( + before: ServiceAccountInfo | undefined, + after: ServiceAccountInfo | undefined, + predicate: StateChangePredicate, + errorMessage?: string, +): void { + expectStateChange(before, after, predicate, errorMessage); +} diff --git a/packages/jammin-sdk/utils/generate-service-output.ts b/packages/jammin-sdk/utils/generate-service-output.ts index fb6876b..532e543 100644 --- a/packages/jammin-sdk/utils/generate-service-output.ts +++ b/packages/jammin-sdk/utils/generate-service-output.ts @@ -1,6 +1,6 @@ import { join, resolve } from "node:path"; import { - type preimage, + type PreimageHash, type ServiceId as ServiceIdType, tryAsServiceGas, tryAsServiceId, @@ -19,8 +19,8 @@ export interface ServiceBuildOutput { code: BytesBlob; storage?: Record; info?: Partial; - preimageBlobs?: HashDictionary; - preimageRequests?: Map; + preimageBlobs?: HashDictionary; + preimageRequests?: Map; } /** @@ -111,7 +111,7 @@ export async function generateServiceOutput( ]), ); - const preimageRequestsMap = new Map( + const preimageRequestsMap = new Map( Object.entries(preimageRequests ?? {}).map(([hash, slots]) => [ Bytes.parseBytes(hash, HASH_SIZE).asOpaque(), tryAsLookupHistorySlots(slots.map((slot) => Slot(slot))), diff --git a/packages/jammin-sdk/work-report.ts b/packages/jammin-sdk/work-report.ts index 701c36e..21e46c9 100644 --- a/packages/jammin-sdk/work-report.ts +++ b/packages/jammin-sdk/work-report.ts @@ -1,36 +1,36 @@ import { type CoreIndex, - refineContext, + MAX_NUMBER_OF_WORK_ITEMS, + MIN_NUMBER_OF_WORK_ITEMS, + RefineContext, type ServiceGas, type ServiceId, - workPackage, - workReport, - workResult, + WorkExecResult, + WorkExecResultKind, + type WorkPackageInfo, + WorkPackageSpec, + WorkRefineLoad, + WorkReport, + WorkResult, } from "@typeberry/lib/block"; + +// Re-export WorkReport type for use in other modules +export type { WorkReport }; + import { BytesBlob } from "@typeberry/lib/bytes"; import type { CodecRecord } from "@typeberry/lib/codec"; import { FixedSizeArray } from "@typeberry/lib/collections"; import { type Blake2b, type Blake2bHash, ZERO_HASH } from "@typeberry/lib/hash"; import { CoreId, Gas, Slot, U8, U16, U32 } from "./types.js"; -type RefineContext = ReturnType; -type WorkPackageInfo = ReturnType; -type WorkPackageSpec = ReturnType; -type WorkReport = ReturnType; -type WorkRefineLoad = ReturnType; -type WorkResult = ReturnType; - -// Re-export types for convenience -export type { RefineContext, WorkPackageInfo, WorkPackageSpec, WorkReport, WorkRefineLoad, WorkResult }; - -const { WorkExecResult, WorkExecResultKind } = workResult; - /** Work result status types */ export type WorkResultStatus = | { type: "ok"; output?: BytesBlob } | { type: "panic" } | { type: "outOfGas" } | { type: "badCode" } + | { type: "digestTooBig" } + | { type: "incorrectNumberOfExports" } | { type: "codeOversize" }; /** Configuration for a single work result with optional fields */ @@ -91,38 +91,41 @@ export function createWorkResult(blake2b: Blake2b, config: WorkResultConfig): Wo const payloadHash = blake2b.hashBytes(payloadBlob); const resultStatus = config.result ?? { type: "ok" }; - let execResult: InstanceType; + let execResult: WorkExecResult; switch (resultStatus.type) { case "ok": - execResult = new WorkExecResult( - WorkExecResultKind.ok, - resultStatus.output ?? BytesBlob.blobFrom(new Uint8Array()), - ); + execResult = WorkExecResult.ok(resultStatus.output ?? BytesBlob.blobFrom(new Uint8Array())); break; case "panic": - execResult = new WorkExecResult(WorkExecResultKind.panic); + execResult = WorkExecResult.error(WorkExecResultKind.panic); break; case "outOfGas": - execResult = new WorkExecResult(WorkExecResultKind.outOfGas); + execResult = WorkExecResult.error(WorkExecResultKind.outOfGas); break; case "badCode": - execResult = new WorkExecResult(WorkExecResultKind.badCode); + execResult = WorkExecResult.error(WorkExecResultKind.badCode); + break; + case "digestTooBig": + execResult = WorkExecResult.error(WorkExecResultKind.digestTooBig); + break; + case "incorrectNumberOfExports": + execResult = WorkExecResult.error(WorkExecResultKind.incorrectNumberOfExports); break; case "codeOversize": - execResult = new WorkExecResult(WorkExecResultKind.codeOversize); + execResult = WorkExecResult.error(WorkExecResultKind.codeOversize); break; } const load = config.load ?? {}; - return workResult.WorkResult.create({ + return WorkResult.create({ serviceId: config.serviceId, codeHash: (config.codeHash ?? ZERO_HASH).asOpaque(), payloadHash, gas: config.gas ?? Gas(0n), result: execResult, - load: workResult.WorkRefineLoad.create({ + load: WorkRefineLoad.create({ gasUsed: load.gasUsed ?? Gas(0n), importedSegments: load.importedSegments ?? U32(0), exportedSegments: load.exportedSegments ?? U32(0), @@ -155,12 +158,12 @@ export function createWorkResult(blake2b: Blake2b, config: WorkResultConfig): Wo * ``` */ export function createWorkReport(blake2b: Blake2b, config: WorkReportConfig): WorkReport { - if (config.results.length < workPackage.MIN_NUMBER_OF_WORK_ITEMS) { - throw new Error(`WorkReport cannot contain less than ${workPackage.MIN_NUMBER_OF_WORK_ITEMS} results`); + if (config.results.length < MIN_NUMBER_OF_WORK_ITEMS) { + throw new Error(`WorkReport cannot contain less than ${MIN_NUMBER_OF_WORK_ITEMS} results`); } - if (config.results.length > workPackage.MAX_NUMBER_OF_WORK_ITEMS) { - throw new Error(`WorkReport cannot contain more than ${workPackage.MAX_NUMBER_OF_WORK_ITEMS} results`); + if (config.results.length > MAX_NUMBER_OF_WORK_ITEMS) { + throw new Error(`WorkReport cannot contain more than ${MAX_NUMBER_OF_WORK_ITEMS} results`); } const results = config.results.map((resultConfig) => createWorkResult(blake2b, resultConfig)); @@ -168,15 +171,15 @@ export function createWorkReport(blake2b: Blake2b, config: WorkReportConfig): Wo const wpSpec = config.workPackageSpec ?? {}; const ctx = config.context ?? {}; - return workReport.WorkReport.create({ - workPackageSpec: workReport.WorkPackageSpec.create({ + return WorkReport.create({ + workPackageSpec: WorkPackageSpec.create({ hash: wpSpec.hash ?? ZERO_HASH.asOpaque(), length: wpSpec.length ?? U32(0), erasureRoot: wpSpec.erasureRoot ?? ZERO_HASH.asOpaque(), exportsRoot: wpSpec.exportsRoot ?? ZERO_HASH.asOpaque(), exportsCount: wpSpec.exportsCount ?? U16(0), }), - context: refineContext.RefineContext.create({ + context: RefineContext.create({ anchor: ctx.anchor ?? ZERO_HASH.asOpaque(), stateRoot: ctx.stateRoot ?? ZERO_HASH.asOpaque(), beefyRoot: ctx.beefyRoot ?? ZERO_HASH.asOpaque(),