@typeberry/lib 0.5.10-6923c44 → 0.5.10-7338c21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/packages/jam/block/work-package.d.ts +7 -7
  3. package/packages/jam/block/work-package.d.ts.map +1 -1
  4. package/packages/jam/block/work-package.js +12 -12
  5. package/packages/jam/executor/pvm-executor.d.ts +7 -0
  6. package/packages/jam/executor/pvm-executor.d.ts.map +1 -1
  7. package/packages/jam/executor/pvm-executor.js +15 -0
  8. package/packages/jam/in-core/externalities/refine.d.ts +3 -3
  9. package/packages/jam/in-core/externalities/refine.d.ts.map +1 -1
  10. package/packages/jam/in-core/externalities/refine.js +33 -5
  11. package/packages/jam/in-core/externalities/refine.test.js +65 -0
  12. package/packages/jam/in-core/in-core.d.ts +7 -22
  13. package/packages/jam/in-core/in-core.d.ts.map +1 -1
  14. package/packages/jam/in-core/in-core.js +16 -184
  15. package/packages/jam/in-core/in-core.test.js +47 -15
  16. package/packages/jam/in-core/is-authorized.d.ts +33 -0
  17. package/packages/jam/in-core/is-authorized.d.ts.map +1 -0
  18. package/packages/jam/in-core/is-authorized.js +72 -0
  19. package/packages/jam/in-core/is-authorized.test.d.ts +2 -0
  20. package/packages/jam/in-core/is-authorized.test.d.ts.map +1 -0
  21. package/packages/jam/in-core/is-authorized.test.js +125 -0
  22. package/packages/jam/in-core/refine.d.ts +34 -0
  23. package/packages/jam/in-core/refine.d.ts.map +1 -0
  24. package/packages/jam/in-core/refine.js +176 -0
  25. package/packages/jam/in-core/refine.test.d.ts +2 -0
  26. package/packages/jam/in-core/refine.test.d.ts.map +1 -0
  27. package/packages/jam/in-core/refine.test.js +6 -0
  28. package/packages/jam/jam-host-calls/accumulate/bless.js +9 -9
  29. package/packages/jam/jam-host-calls/externalities/partial-state.d.ts +1 -1
  30. package/packages/jam/jam-host-calls/general/fetch.d.ts +40 -40
  31. package/packages/jam/jam-host-calls/general/fetch.d.ts.map +1 -1
  32. package/packages/jam/jam-host-calls/general/fetch.js +30 -30
  33. package/packages/jam/jam-host-calls/general/fetch.test.js +17 -14
  34. package/packages/jam/jamnp-s/protocol/ce-133-work-package-submission.d.ts +2 -2
  35. package/packages/jam/transition/accumulate/accumulation-result-merge-utils.js +48 -39
  36. package/packages/jam/transition/externalities/accumulate-externalities.d.ts +2 -2
  37. package/packages/jam/transition/externalities/accumulate-externalities.d.ts.map +1 -1
  38. package/packages/jam/transition/externalities/accumulate-externalities.js +20 -7
  39. package/packages/jam/transition/externalities/accumulate-externalities.test.js +74 -4
  40. package/packages/jam/transition/externalities/index.d.ts +1 -0
  41. package/packages/jam/transition/externalities/index.d.ts.map +1 -1
  42. package/packages/jam/transition/externalities/index.js +1 -0
  43. package/packages/jam/transition/externalities/is-authorized-fetch-externalities.d.ts +22 -0
  44. package/packages/jam/transition/externalities/is-authorized-fetch-externalities.d.ts.map +1 -0
  45. package/packages/jam/transition/externalities/is-authorized-fetch-externalities.js +41 -0
  46. package/packages/jam/transition/externalities/refine-fetch-externalities.d.ts +7 -7
  47. package/packages/jam/transition/externalities/refine-fetch-externalities.d.ts.map +1 -1
  48. package/packages/jam/transition/externalities/refine-fetch-externalities.js +17 -9
@@ -1,18 +1,13 @@
1
- import { tryAsCoreIndex, tryAsServiceGas, } from "#@typeberry/block";
2
- import { W_C } from "#@typeberry/block/gp-constants.js";
3
- import { WorkPackageInfo, } from "#@typeberry/block/refine-context.js";
1
+ import { WorkPackageInfo } from "#@typeberry/block/refine-context.js";
4
2
  import { WorkPackageSpec, WorkReport } from "#@typeberry/block/work-report.js";
5
- import { WorkExecResult, WorkExecResultKind, WorkRefineLoad, WorkResult } from "#@typeberry/block/work-result.js";
6
- import { Bytes, BytesBlob } from "#@typeberry/bytes";
7
- import { codec, Encoder } from "#@typeberry/codec";
3
+ import { Bytes } from "#@typeberry/bytes";
8
4
  import { asKnownSize, FixedSizeArray } from "#@typeberry/collections";
9
- import { PvmExecutor, ReturnStatus } from "#@typeberry/executor";
10
5
  import { HASH_SIZE } from "#@typeberry/hash";
11
6
  import { Logger } from "#@typeberry/logger";
12
7
  import { tryAsU8, tryAsU16, tryAsU32 } from "#@typeberry/numbers";
13
- import { RefineFetchExternalities } from "#@typeberry/transition/externalities/refine-fetch-externalities.js";
14
- import { assertEmpty, assertNever, Result } from "#@typeberry/utils";
15
- import { RefineExternalitiesImpl } from "./externalities/refine.js";
8
+ import { assertEmpty, Result } from "#@typeberry/utils";
9
+ import { AuthorizationError, IsAuthorized } from "./is-authorized.js";
10
+ import { Refine } from "./refine.js";
16
11
  export var RefineError;
17
12
  (function (RefineError) {
18
13
  /** State for context anchor block or lookup anchor is not found in the DB. */
@@ -24,39 +19,17 @@ export var RefineError;
24
19
  /** Authorization error. */
25
20
  RefineError[RefineError["AuthorizationError"] = 3] = "AuthorizationError";
26
21
  })(RefineError || (RefineError = {}));
27
- var ServiceCodeError;
28
- (function (ServiceCodeError) {
29
- /** Service id is not found in the state. */
30
- ServiceCodeError[ServiceCodeError["ServiceNotFound"] = 0] = "ServiceNotFound";
31
- /** Expected service code does not match the state one. */
32
- ServiceCodeError[ServiceCodeError["ServiceCodeMismatch"] = 1] = "ServiceCodeMismatch";
33
- /** Code preimage missing. */
34
- ServiceCodeError[ServiceCodeError["ServiceCodeMissing"] = 2] = "ServiceCodeMissing";
35
- /** Code blob is too big. */
36
- ServiceCodeError[ServiceCodeError["ServiceCodeTooBig"] = 3] = "ServiceCodeTooBig";
37
- })(ServiceCodeError || (ServiceCodeError = {}));
38
- var AuthorizationError;
39
- (function (AuthorizationError) {
40
- })(AuthorizationError || (AuthorizationError = {}));
41
22
  const logger = Logger.new(import.meta.filename, "refine");
42
- /** https://graypaper.fluffylabs.dev/#/ab2cdbd/2ffe002ffe00?v=0.7.2 */
43
- const ARGS_CODEC = codec.object({
44
- core: codec.varU32.convert((x) => tryAsU32(x), (x) => tryAsCoreIndex(x)),
45
- workItemIndex: codec.varU32,
46
- serviceId: codec.varU32.asOpaque(),
47
- payloadLength: codec.varU32,
48
- packageHash: codec.bytes(HASH_SIZE).asOpaque(),
49
- });
50
23
  export class InCore {
51
24
  chainSpec;
52
25
  states;
53
- pvmBackend;
54
- blake2b;
26
+ isAuthorized;
27
+ refineItem;
55
28
  constructor(chainSpec, states, pvmBackend, blake2b) {
56
29
  this.chainSpec = chainSpec;
57
30
  this.states = states;
58
- this.pvmBackend = pvmBackend;
59
- this.blake2b = blake2b;
31
+ this.isAuthorized = new IsAuthorized(chainSpec, pvmBackend, blake2b);
32
+ this.refineItem = new Refine(chainSpec, pvmBackend, blake2b);
60
33
  }
61
34
  /**
62
35
  * Work-report computation function.
@@ -70,13 +43,14 @@ export class InCore {
70
43
  */
71
44
  async refine(workPackageAndHash, core, imports, extrinsics) {
72
45
  const workPackageHash = workPackageAndHash.hash;
73
- const { context, authorization, authCodeHash, authCodeHost, parametrization, items, ...rest } = workPackageAndHash.data;
46
+ const { context, authToken, authCodeHash, authCodeHost, authConfiguration, items, ...rest } = workPackageAndHash.data;
74
47
  assertEmpty(rest);
75
48
  // TODO [ToDr] Verify BEEFY root
76
49
  // TODO [ToDr] Verify prerequisites
77
50
  logger.log `[core:${core}] Attempting to refine work package with ${items.length} items.`;
78
- // TODO [ToDr] GP link
79
51
  // Verify anchor block
52
+ // https://graypaper.fluffylabs.dev/#/ab2cdbd/15cd0215cd02?v=0.7.2
53
+ // TODO [ToDr] Validation
80
54
  const state = this.states.getState(context.anchor);
81
55
  if (state === null) {
82
56
  return Result.error(RefineError.StateMissing, () => `State at anchor block ${context.anchor} is missing.`);
@@ -96,7 +70,7 @@ export class InCore {
96
70
  return Result.error(RefineError.InvalidLookupAnchorSlot, () => `Lookup anchor slot does not match the one is state. Ours: ${lookupState.timeslot}, expected: ${context.lookupAnchorSlot}`);
97
71
  }
98
72
  // Check authorization
99
- const authResult = await this.authorizePackage(authorization, authCodeHost, authCodeHash, parametrization);
73
+ const authResult = await this.isAuthorized.invoke(state, core, authToken, authCodeHost, authCodeHash, authConfiguration);
100
74
  if (authResult.isError) {
101
75
  return Result.error(RefineError.AuthorizationError, () => `Authorization error: ${AuthorizationError[authResult.error]}: ${authResult.details()}.`);
102
76
  }
@@ -106,14 +80,14 @@ export class InCore {
106
80
  const refineResults = [];
107
81
  for (const [idx, item] of items.entries()) {
108
82
  logger.info `[core:${core}][i:${idx}] Refining item for service ${item.service}.`;
109
- const result = await this.refineItem(state, lookupState, idx, item, imports, extrinsics, core, workPackageHash, exportOffset);
83
+ const result = await this.refineItem.invoke(state, lookupState, idx, item, imports, extrinsics, core, workPackageHash, exportOffset);
110
84
  refineResults.push(result);
111
85
  exportOffset += result.exports.length;
112
86
  }
113
87
  // amalgamate the work report now
114
- return Result.ok(this.amalgamateWorkReport(asKnownSize(refineResults), authResult.ok, workPackageHash, context, core));
88
+ return Result.ok(InCore.amalgamateWorkReport(asKnownSize(refineResults), authResult.ok, workPackageHash, context, core));
115
89
  }
116
- amalgamateWorkReport(refineResults, authResult, workPackageHash, context, coreIndex) {
90
+ static amalgamateWorkReport(refineResults, authResult, workPackageHash, context, coreIndex) {
117
91
  // unzip exports and work results for each work item
118
92
  const exports = refineResults.map((x) => x.exports);
119
93
  const results = refineResults.map((x) => x.result);
@@ -156,146 +130,4 @@ export class InCore {
156
130
  exports: asKnownSize(exports),
157
131
  };
158
132
  }
159
- async authorizePackage(_authorization, _authCodeHost, _authCodeHash, _parametrization) {
160
- // TODO [ToDr] Check authorization?
161
- const authorizerHash = Bytes.zero(HASH_SIZE).asOpaque();
162
- const authorizationGasUsed = tryAsServiceGas(0);
163
- const authorizationOutput = BytesBlob.empty();
164
- return Result.ok({
165
- authorizerHash,
166
- authorizationGasUsed,
167
- authorizationOutput,
168
- });
169
- }
170
- async refineItem(state, lookupState, idx, item, allImports, allExtrinsics, coreIndex, workPackageHash, exportOffset) {
171
- const payloadHash = this.blake2b.hashBytes(item.payload);
172
- const baseResult = {
173
- serviceId: item.service,
174
- codeHash: item.codeHash,
175
- payloadHash,
176
- gas: item.refineGasLimit,
177
- };
178
- const imports = allImports[idx];
179
- const extrinsics = allExtrinsics[idx];
180
- const baseLoad = {
181
- importedSegments: tryAsU32(imports.length),
182
- extrinsicCount: tryAsU32(extrinsics.length),
183
- extrinsicSize: tryAsU32(extrinsics.reduce((acc, x) => acc + x.length, 0)),
184
- };
185
- const maybeCode = this.getServiceCode(state, idx, item);
186
- if (maybeCode.isError) {
187
- const error = maybeCode.error === ServiceCodeError.ServiceCodeTooBig
188
- ? WorkExecResultKind.codeOversize
189
- : WorkExecResultKind.badCode;
190
- return {
191
- exports: [],
192
- result: WorkResult.create({
193
- ...baseResult,
194
- result: WorkExecResult.error(error),
195
- load: WorkRefineLoad.create({
196
- ...baseLoad,
197
- gasUsed: tryAsServiceGas(item.refineGasLimit),
198
- exportedSegments: tryAsU32(0),
199
- }),
200
- }),
201
- };
202
- }
203
- const code = maybeCode.ok;
204
- const externalities = this.createRefineExternalities({
205
- payload: item.payload,
206
- imports: allImports,
207
- extrinsics: allExtrinsics,
208
- currentServiceId: item.service,
209
- lookupState,
210
- exportOffset,
211
- });
212
- const executor = await PvmExecutor.createRefineExecutor(item.service, code, externalities, this.pvmBackend);
213
- const args = Encoder.encodeObject(ARGS_CODEC, {
214
- serviceId: item.service,
215
- core: coreIndex,
216
- workItemIndex: tryAsU32(idx),
217
- payloadLength: tryAsU32(item.payload.length),
218
- packageHash: workPackageHash,
219
- });
220
- const execResult = await executor.run(args, item.refineGasLimit);
221
- const exports = externalities.refine.getExportedSegments();
222
- if (exports.length !== item.exportCount) {
223
- return {
224
- exports,
225
- result: WorkResult.create({
226
- ...baseResult,
227
- result: WorkExecResult.error(WorkExecResultKind.incorrectNumberOfExports),
228
- load: WorkRefineLoad.create({
229
- ...baseLoad,
230
- gasUsed: tryAsServiceGas(item.refineGasLimit),
231
- exportedSegments: tryAsU32(0),
232
- }),
233
- }),
234
- };
235
- }
236
- const result = this.extractWorkResult(execResult);
237
- return {
238
- exports,
239
- result: WorkResult.create({
240
- ...baseResult,
241
- result,
242
- load: WorkRefineLoad.create({
243
- ...baseLoad,
244
- gasUsed: tryAsServiceGas(execResult.consumedGas),
245
- exportedSegments: tryAsU32(exports.length),
246
- }),
247
- }),
248
- };
249
- }
250
- extractWorkResult(execResult) {
251
- if (execResult.status === ReturnStatus.OK) {
252
- const slice = execResult.memorySlice;
253
- // TODO [ToDr] Verify the output size and change digestTooBig?
254
- return WorkExecResult.ok(BytesBlob.blobFrom(slice));
255
- }
256
- switch (execResult.status) {
257
- case ReturnStatus.OOG:
258
- return WorkExecResult.error(WorkExecResultKind.outOfGas);
259
- case ReturnStatus.PANIC:
260
- return WorkExecResult.error(WorkExecResultKind.panic);
261
- default:
262
- assertNever(execResult);
263
- }
264
- }
265
- getServiceCode(state, idx, item) {
266
- const serviceId = item.service;
267
- const service = state.getService(serviceId);
268
- // TODO [ToDr] GP link
269
- // missing service
270
- if (service === null) {
271
- return Result.error(ServiceCodeError.ServiceNotFound, () => `[i:${idx}] Service ${serviceId} is missing in state.`);
272
- }
273
- // TODO [ToDr] GP link
274
- // TODO [ToDr] shall we rather use the old codehash instead
275
- if (!service.getInfo().codeHash.isEqualTo(item.codeHash)) {
276
- return Result.error(ServiceCodeError.ServiceCodeMismatch, () => `[i:${idx}] Service ${serviceId} has invalid code hash. Ours: ${service.getInfo().codeHash}, expected: ${item.codeHash}`);
277
- }
278
- const code = service.getPreimage(item.codeHash.asOpaque());
279
- if (code === null) {
280
- return Result.error(ServiceCodeError.ServiceCodeMissing, () => `[i:${idx}] Code ${item.codeHash} for service ${serviceId} was not found.`);
281
- }
282
- if (code.length > W_C) {
283
- return Result.error(ServiceCodeError.ServiceCodeTooBig, () => `[i:${idx}] Code ${item.codeHash} for service ${serviceId} is too big! ${code.length} bytes vs ${W_C} bytes max.`);
284
- }
285
- return Result.ok(code);
286
- }
287
- createRefineExternalities(args) {
288
- // TODO [ToDr] Pass all required fetch data
289
- const fetchExternalities = new RefineFetchExternalities(this.chainSpec);
290
- const refine = RefineExternalitiesImpl.create({
291
- currentServiceId: args.currentServiceId,
292
- lookupState: args.lookupState,
293
- exportOffset: args.exportOffset,
294
- pvmBackend: this.pvmBackend,
295
- });
296
- return {
297
- fetchExternalities,
298
- refine,
299
- };
300
- }
301
133
  }
@@ -1,4 +1,6 @@
1
1
  import assert from "node:assert";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
2
4
  import { before, describe, it } from "node:test";
3
5
  import { tryAsCoreIndex, tryAsServiceGas, tryAsServiceId, tryAsTimeSlot } from "#@typeberry/block";
4
6
  import { RefineContext } from "#@typeberry/block/refine-context.js";
@@ -6,21 +8,46 @@ import { WorkItem } from "#@typeberry/block/work-item.js";
6
8
  import { tryAsWorkItemsCount, WorkPackage } from "#@typeberry/block/work-package.js";
7
9
  import { Bytes, BytesBlob } from "#@typeberry/bytes";
8
10
  import { Encoder } from "#@typeberry/codec";
9
- import { asKnownSize, FixedSizeArray } from "#@typeberry/collections";
11
+ import { asKnownSize, FixedSizeArray, HashDictionary } from "#@typeberry/collections";
10
12
  import { PvmBackend, tinyChainSpec } from "#@typeberry/config";
11
13
  import { InMemoryStates } from "#@typeberry/database";
12
14
  import { Blake2b, HASH_SIZE, WithHash } from "#@typeberry/hash";
13
- import { tryAsU16 } from "#@typeberry/numbers";
14
- import { testState } from "#@typeberry/state/test.utils.js";
15
+ import { tryAsU16, tryAsU32, tryAsU64 } from "#@typeberry/numbers";
16
+ import { InMemoryService, InMemoryState, PreimageItem, ServiceAccountInfo } from "#@typeberry/state";
15
17
  import { InCore, RefineError } from "./in-core.js";
18
+ // Load the authorizer PVM fixture (checks authToken === authConfiguration).
19
+ const AUTHORIZER_PVM = BytesBlob.blobFrom(readFileSync(resolve(import.meta.dirname, "fixtures/authorizer.pvm")));
20
+ const AUTH_SERVICE_ID = tryAsServiceId(1);
16
21
  let blake2b;
17
22
  before(async () => {
18
23
  blake2b = await Blake2b.createHasher();
19
24
  });
20
- function createWorkItem(serviceId = 1) {
25
+ function getAuthCodeHash() {
26
+ return blake2b.hashBytes(AUTHORIZER_PVM).asOpaque();
27
+ }
28
+ function createService(serviceId, codeHash, code) {
29
+ return new InMemoryService(serviceId, {
30
+ info: ServiceAccountInfo.create({
31
+ codeHash: codeHash.asOpaque(),
32
+ balance: tryAsU64(10_000_000_000),
33
+ accumulateMinGas: tryAsServiceGas(0n),
34
+ onTransferMinGas: tryAsServiceGas(0n),
35
+ storageUtilisationBytes: tryAsU64(0),
36
+ storageUtilisationCount: tryAsU32(0),
37
+ gratisStorage: tryAsU64(0),
38
+ created: tryAsTimeSlot(0),
39
+ lastAccumulation: tryAsTimeSlot(0),
40
+ parentService: tryAsServiceId(0),
41
+ }),
42
+ preimages: HashDictionary.fromEntries([PreimageItem.create({ hash: codeHash.asOpaque(), blob: code })].map((x) => [x.hash, x])),
43
+ lookupHistory: HashDictionary.fromEntries([]),
44
+ storage: new Map(),
45
+ });
46
+ }
47
+ function createWorkItem(codeHash, serviceId = 1) {
21
48
  return WorkItem.create({
22
49
  service: tryAsServiceId(serviceId),
23
- codeHash: Bytes.zero(HASH_SIZE).asOpaque(),
50
+ codeHash,
24
51
  payload: BytesBlob.empty(),
25
52
  refineGasLimit: tryAsServiceGas(1_000_000),
26
53
  accumulateGasLimit: tryAsServiceGas(1_000_000),
@@ -29,12 +56,12 @@ function createWorkItem(serviceId = 1) {
29
56
  exportCount: tryAsU16(0),
30
57
  });
31
58
  }
32
- function createWorkPackage(anchorHash, stateRoot, lookupAnchorSlot = 0) {
59
+ function createWorkPackage(anchorHash, stateRoot, authCodeHash, lookupAnchorSlot = 0) {
33
60
  return WorkPackage.create({
34
- authorization: BytesBlob.empty(),
35
- authCodeHost: tryAsServiceId(1),
36
- authCodeHash: Bytes.zero(HASH_SIZE).asOpaque(),
37
- parametrization: BytesBlob.empty(),
61
+ authToken: BytesBlob.empty(),
62
+ authCodeHost: AUTH_SERVICE_ID,
63
+ authCodeHash,
64
+ authConfiguration: BytesBlob.empty(),
38
65
  context: RefineContext.create({
39
66
  anchor: anchorHash,
40
67
  stateRoot,
@@ -43,7 +70,7 @@ function createWorkPackage(anchorHash, stateRoot, lookupAnchorSlot = 0) {
43
70
  lookupAnchorSlot: tryAsTimeSlot(lookupAnchorSlot),
44
71
  prerequisites: [],
45
72
  }),
46
- items: FixedSizeArray.new([createWorkItem()], tryAsWorkItemsCount(1)),
73
+ items: FixedSizeArray.new([createWorkItem(authCodeHash)], tryAsWorkItemsCount(1)),
47
74
  });
48
75
  }
49
76
  function hashWorkPackage(spec, workPackage) {
@@ -59,7 +86,8 @@ describe("InCore", () => {
59
86
  const inCore = new InCore(spec, states, PvmBackend.BuiltIn, blake2b);
60
87
  const anchorHash = Bytes.fill(HASH_SIZE, 1).asOpaque();
61
88
  const stateRoot = Bytes.zero(HASH_SIZE).asOpaque();
62
- const workPackage = createWorkPackage(anchorHash, stateRoot);
89
+ const authCodeHash = getAuthCodeHash();
90
+ const workPackage = createWorkPackage(anchorHash, stateRoot, authCodeHash);
63
91
  const result = await inCore.refine(hashWorkPackage(spec, workPackage), tryAsCoreIndex(0), asKnownSize([[]]), asKnownSize([[]]));
64
92
  assert.strictEqual(result.isError, true);
65
93
  assert.strictEqual(result.error, RefineError.StateMissing);
@@ -68,13 +96,17 @@ describe("InCore", () => {
68
96
  const spec = tinyChainSpec;
69
97
  const states = new InMemoryStates(spec);
70
98
  const inCore = new InCore(spec, states, PvmBackend.BuiltIn, blake2b);
99
+ const authCodeHash = getAuthCodeHash();
71
100
  const anchorHash = Bytes.fill(HASH_SIZE, 1).asOpaque();
72
- const state = testState();
101
+ const state = InMemoryState.partial(spec, {
102
+ timeslot: tryAsTimeSlot(16),
103
+ services: new Map([[AUTH_SERVICE_ID, createService(AUTH_SERVICE_ID, authCodeHash, AUTHORIZER_PVM)]]),
104
+ });
73
105
  await states.insertInitialState(anchorHash, state);
74
106
  const correctStateRoot = await states.getStateRoot(state);
75
- const workPackage = createWorkPackage(anchorHash, correctStateRoot, state.timeslot);
107
+ const workPackage = createWorkPackage(anchorHash, correctStateRoot, authCodeHash, state.timeslot);
76
108
  const result = await inCore.refine(hashWorkPackage(spec, workPackage), tryAsCoreIndex(0), asKnownSize([[]]), asKnownSize([[]]));
77
- assert.strictEqual(result.isOk, true);
109
+ assert.strictEqual(result.isOk, true, `Expected OK but got error: ${result.isError ? result.details() : ""}`);
78
110
  assert.strictEqual(result.ok.report.coreIndex, 0);
79
111
  assert.strictEqual(result.ok.report.results.length, 1);
80
112
  });
@@ -0,0 +1,33 @@
1
+ import { type CodeHash, type CoreIndex, type ServiceGas, type ServiceId } from "#@typeberry/block";
2
+ import type { AuthorizerHash } from "#@typeberry/block/refine-context.js";
3
+ import { BytesBlob } from "#@typeberry/bytes";
4
+ import type { ChainSpec, PvmBackend } from "#@typeberry/config";
5
+ import type { Blake2b } from "#@typeberry/hash";
6
+ import type { State } from "#@typeberry/state";
7
+ import { Result } from "#@typeberry/utils";
8
+ export declare enum AuthorizationError {
9
+ /** BAD: authorizer code not found (service or preimage missing). */
10
+ CodeNotFound = 0,
11
+ /** BIG: authorizer code exceeds W_A limit. */
12
+ CodeTooBig = 1,
13
+ /** PANIC/OOG: PVM execution failed. */
14
+ PvmFailed = 2
15
+ }
16
+ export type AuthorizationOk = {
17
+ authorizerHash: AuthorizerHash;
18
+ authorizationGasUsed: ServiceGas;
19
+ authorizationOutput: BytesBlob;
20
+ };
21
+ /**
22
+ * IsAuthorized PVM invocation (Psi_I).
23
+ *
24
+ * https://graypaper.fluffylabs.dev/#/ab2cdbd/2e64002e6400?v=0.7.2
25
+ */
26
+ export declare class IsAuthorized {
27
+ private readonly chainSpec;
28
+ private readonly pvmBackend;
29
+ private readonly blake2b;
30
+ constructor(chainSpec: ChainSpec, pvmBackend: PvmBackend, blake2b: Blake2b);
31
+ invoke(state: State, coreIndex: CoreIndex, authToken: BytesBlob, authCodeHost: ServiceId, authCodeHash: CodeHash, authConfiguration: BytesBlob): Promise<Result<AuthorizationOk, AuthorizationError>>;
32
+ }
33
+ //# sourceMappingURL=is-authorized.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-authorized.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/in-core/is-authorized.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAmB,MAAM,kBAAkB,CAAC;AAEnH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,oBAAY,kBAAkB;IAC5B,oEAAoE;IACpE,YAAY,IAAI;IAChB,8CAA8C;IAC9C,UAAU,IAAI;IACd,uCAAuC;IACvC,SAAS,IAAI;CACd;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,cAAc,CAAC;IAC/B,oBAAoB,EAAE,UAAU,CAAC;IACjC,mBAAmB,EAAE,SAAS,CAAC;CAChC,CAAC;AAMF;;;;GAIG;AACH,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAFP,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO;IAG7B,MAAM,CACV,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,SAAS,EACvB,YAAY,EAAE,QAAQ,EACtB,iBAAiB,EAAE,SAAS,GAC3B,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;CA+DxD"}
@@ -0,0 +1,72 @@
1
+ import { tryAsServiceGas } from "#@typeberry/block";
2
+ import { G_I, W_A } from "#@typeberry/block/gp-constants.js";
3
+ import { BytesBlob } from "#@typeberry/bytes";
4
+ import { codec, Encoder } from "#@typeberry/codec";
5
+ import { PvmExecutor, ReturnStatus } from "#@typeberry/executor";
6
+ import { IsAuthorizedFetchExternalities } from "#@typeberry/transition/externalities/is-authorized-fetch-externalities.js";
7
+ import { Result } from "#@typeberry/utils";
8
+ export var AuthorizationError;
9
+ (function (AuthorizationError) {
10
+ /** BAD: authorizer code not found (service or preimage missing). */
11
+ AuthorizationError[AuthorizationError["CodeNotFound"] = 0] = "CodeNotFound";
12
+ /** BIG: authorizer code exceeds W_A limit. */
13
+ AuthorizationError[AuthorizationError["CodeTooBig"] = 1] = "CodeTooBig";
14
+ /** PANIC/OOG: PVM execution failed. */
15
+ AuthorizationError[AuthorizationError["PvmFailed"] = 2] = "PvmFailed";
16
+ })(AuthorizationError || (AuthorizationError = {}));
17
+ const AUTH_ARGS_CODEC = codec.object({
18
+ coreIndex: codec.u16,
19
+ });
20
+ /**
21
+ * IsAuthorized PVM invocation (Psi_I).
22
+ *
23
+ * https://graypaper.fluffylabs.dev/#/ab2cdbd/2e64002e6400?v=0.7.2
24
+ */
25
+ export class IsAuthorized {
26
+ chainSpec;
27
+ pvmBackend;
28
+ blake2b;
29
+ constructor(chainSpec, pvmBackend, blake2b) {
30
+ this.chainSpec = chainSpec;
31
+ this.pvmBackend = pvmBackend;
32
+ this.blake2b = blake2b;
33
+ }
34
+ async invoke(state, coreIndex, authToken, authCodeHost, authCodeHash, authConfiguration) {
35
+ // Look up the authorizer code from the auth code host service
36
+ const service = state.getService(authCodeHost);
37
+ // https://graypaper.fluffylabs.dev/#/ab2cdbd/2eca002eca00?v=0.7.2
38
+ if (service === null) {
39
+ return Result.error(AuthorizationError.CodeNotFound, () => `Auth code host service ${authCodeHost} not found in state.`);
40
+ }
41
+ const code = service.getPreimage(authCodeHash.asOpaque());
42
+ if (code === null) {
43
+ return Result.error(AuthorizationError.CodeNotFound, () => `Auth code preimage ${authCodeHash} not found in service ${authCodeHost}.`);
44
+ }
45
+ // BIG: code exceeds W_A
46
+ // https://graypaper.fluffylabs.dev/#/ab2cdbd/2ed6002ed600?v=0.7.2
47
+ if (code.length > W_A) {
48
+ return Result.error(AuthorizationError.CodeTooBig, () => `Auth code is too big: ${code.length} bytes vs ${W_A} max.`);
49
+ }
50
+ // Prepare fetch externalities and executor
51
+ const fetchExternalities = new IsAuthorizedFetchExternalities(this.chainSpec, {
52
+ authToken,
53
+ authConfiguration,
54
+ });
55
+ const executor = await PvmExecutor.createIsAuthorizedExecutor(authCodeHost, code, { fetchExternalities }, this.pvmBackend);
56
+ const args = Encoder.encodeObject(AUTH_ARGS_CODEC, {
57
+ coreIndex,
58
+ });
59
+ // Run PVM with gas budget G_I
60
+ const gasLimit = tryAsServiceGas(G_I);
61
+ const execResult = await executor.run(args, gasLimit);
62
+ if (execResult.status !== ReturnStatus.OK) {
63
+ return Result.error(AuthorizationError.PvmFailed, () => `IsAuthorized PVM ${ReturnStatus[execResult.status]} (gas used: ${execResult.consumedGas}).`);
64
+ }
65
+ // Compute authorizer hash: H(code_hash ++ configuration)
66
+ // https://graypaper.fluffylabs.dev/#/ab2cdbd/1b81011b8401?v=0.7.2
67
+ const authorizerHash = this.blake2b.hashBlobs([authCodeHash, authConfiguration]);
68
+ const authorizationOutput = BytesBlob.blobFrom(execResult.memorySlice);
69
+ const authorizationGasUsed = tryAsServiceGas(execResult.consumedGas);
70
+ return Result.ok({ authorizerHash, authorizationGasUsed, authorizationOutput });
71
+ }
72
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=is-authorized.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-authorized.test.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/in-core/is-authorized.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,125 @@
1
+ import assert from "node:assert";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { before, describe, it } from "node:test";
5
+ import { tryAsCoreIndex, tryAsServiceGas, tryAsServiceId, tryAsTimeSlot } from "#@typeberry/block";
6
+ import { Bytes, BytesBlob } from "#@typeberry/bytes";
7
+ import { HashDictionary } from "#@typeberry/collections";
8
+ import { PvmBackend, tinyChainSpec } from "#@typeberry/config";
9
+ import { Blake2b, HASH_SIZE } from "#@typeberry/hash";
10
+ import { tryAsU32, tryAsU64 } from "#@typeberry/numbers";
11
+ import { InMemoryService, InMemoryState, PreimageItem, ServiceAccountInfo } from "#@typeberry/state";
12
+ import { AuthorizationError, IsAuthorized } from "./is-authorized.js";
13
+ let blake2b;
14
+ before(async () => {
15
+ blake2b = await Blake2b.createHasher();
16
+ });
17
+ // Load the authorizer PVM fixture.
18
+ // This authorizer checks that authToken === authConfiguration and returns "Auth=<token>".
19
+ // https://github.com/tomusdrw/as-lan/blob/main/examples/authorizer/assembly/authorize.ts
20
+ const AUTHORIZER_PVM = BytesBlob.blobFrom(readFileSync(resolve(import.meta.dirname, "fixtures/authorizer.pvm")));
21
+ const AUTH_SERVICE_ID = tryAsServiceId(42);
22
+ function createService(serviceId, codeHash, code) {
23
+ return new InMemoryService(serviceId, {
24
+ info: ServiceAccountInfo.create({
25
+ codeHash: codeHash.asOpaque(),
26
+ balance: tryAsU64(10_000_000_000),
27
+ accumulateMinGas: tryAsServiceGas(0n),
28
+ onTransferMinGas: tryAsServiceGas(0n),
29
+ storageUtilisationBytes: tryAsU64(0),
30
+ storageUtilisationCount: tryAsU32(0),
31
+ gratisStorage: tryAsU64(0),
32
+ created: tryAsTimeSlot(0),
33
+ lastAccumulation: tryAsTimeSlot(0),
34
+ parentService: tryAsServiceId(0),
35
+ }),
36
+ preimages: HashDictionary.fromEntries([PreimageItem.create({ hash: codeHash.asOpaque(), blob: code })].map((x) => [x.hash, x])),
37
+ lookupHistory: HashDictionary.fromEntries([]),
38
+ storage: new Map(),
39
+ });
40
+ }
41
+ describe("IsAuthorized", () => {
42
+ const spec = tinyChainSpec;
43
+ function getAuthCodeHash() {
44
+ return blake2b.hashBytes(AUTHORIZER_PVM).asOpaque();
45
+ }
46
+ function createStateWithService(codeHash, code) {
47
+ return InMemoryState.partial(spec, {
48
+ timeslot: tryAsTimeSlot(16),
49
+ services: new Map([[AUTH_SERVICE_ID, createService(AUTH_SERVICE_ID, codeHash, code)]]),
50
+ });
51
+ }
52
+ it("should authorize when token matches configuration", async () => {
53
+ const authCodeHash = getAuthCodeHash();
54
+ const state = createStateWithService(authCodeHash, AUTHORIZER_PVM);
55
+ const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b);
56
+ const token = BytesBlob.blobFromString("hello");
57
+ const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), token, AUTH_SERVICE_ID, authCodeHash, token);
58
+ assert.strictEqual(result.isOk, true, `Expected OK but got error: ${result.isError ? result.details() : ""}`);
59
+ // Verify the authorization output starts with "Auth=<hello>"
60
+ const outputStr = Buffer.from(result.ok.authorizationOutput.raw).toString("utf8");
61
+ assert.ok(outputStr.startsWith("Auth=<hello>"), `Expected "Auth=<hello>" prefix but got "${outputStr.slice(0, 30)}"`);
62
+ // Verify the authorizer hash is H(code_hash ++ configuration)
63
+ const expectedHash = blake2b.hashBlobs([authCodeHash, token]);
64
+ assert.ok(result.ok.authorizerHash.isEqualTo(expectedHash), "authorizerHash should be H(code_hash || config)");
65
+ // Verify gas was consumed
66
+ assert.ok(Number(result.ok.authorizationGasUsed) > 0, "should have consumed some gas");
67
+ });
68
+ it("should authorize with empty token and configuration", async () => {
69
+ const authCodeHash = getAuthCodeHash();
70
+ const state = createStateWithService(authCodeHash, AUTHORIZER_PVM);
71
+ const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b);
72
+ const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), BytesBlob.empty(), AUTH_SERVICE_ID, authCodeHash, BytesBlob.empty());
73
+ assert.strictEqual(result.isOk, true, `Expected OK but got error: ${result.isError ? result.details() : ""}`);
74
+ const outputStr = Buffer.from(result.ok.authorizationOutput.raw).toString("utf8");
75
+ assert.ok(outputStr.startsWith("Auth=<>"), `Expected "Auth=<>" prefix but got "${outputStr.slice(0, 30)}"`);
76
+ });
77
+ it("should fail when token does not match configuration", async () => {
78
+ const authCodeHash = getAuthCodeHash();
79
+ const state = createStateWithService(authCodeHash, AUTHORIZER_PVM);
80
+ const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b);
81
+ const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), BytesBlob.blobFromString("wrong"), AUTH_SERVICE_ID, authCodeHash, BytesBlob.blobFromString("right"));
82
+ assert.strictEqual(result.isError, true);
83
+ assert.strictEqual(result.error, AuthorizationError.PvmFailed);
84
+ });
85
+ it("should fail when auth code host service is missing", async () => {
86
+ const authCodeHash = getAuthCodeHash();
87
+ const state = InMemoryState.partial(spec, {
88
+ timeslot: tryAsTimeSlot(16),
89
+ services: new Map(),
90
+ });
91
+ const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b);
92
+ const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), BytesBlob.empty(), AUTH_SERVICE_ID, authCodeHash, BytesBlob.empty());
93
+ assert.strictEqual(result.isError, true);
94
+ assert.strictEqual(result.error, AuthorizationError.CodeNotFound);
95
+ });
96
+ it("should fail when auth code preimage is missing", async () => {
97
+ const authCodeHash = getAuthCodeHash();
98
+ // Service exists but with no preimages
99
+ const emptyService = new InMemoryService(AUTH_SERVICE_ID, {
100
+ info: ServiceAccountInfo.create({
101
+ codeHash: Bytes.zero(HASH_SIZE).asOpaque(),
102
+ balance: tryAsU64(0),
103
+ accumulateMinGas: tryAsServiceGas(0n),
104
+ onTransferMinGas: tryAsServiceGas(0n),
105
+ storageUtilisationBytes: tryAsU64(0),
106
+ storageUtilisationCount: tryAsU32(0),
107
+ gratisStorage: tryAsU64(0),
108
+ created: tryAsTimeSlot(0),
109
+ lastAccumulation: tryAsTimeSlot(0),
110
+ parentService: tryAsServiceId(0),
111
+ }),
112
+ preimages: HashDictionary.fromEntries([]),
113
+ lookupHistory: HashDictionary.fromEntries([]),
114
+ storage: new Map(),
115
+ });
116
+ const state = InMemoryState.partial(spec, {
117
+ timeslot: tryAsTimeSlot(16),
118
+ services: new Map([[AUTH_SERVICE_ID, emptyService]]),
119
+ });
120
+ const isAuthorized = new IsAuthorized(spec, PvmBackend.BuiltIn, blake2b);
121
+ const result = await isAuthorized.invoke(state, tryAsCoreIndex(0), BytesBlob.empty(), AUTH_SERVICE_ID, authCodeHash, BytesBlob.empty());
122
+ assert.strictEqual(result.isError, true);
123
+ assert.strictEqual(result.error, AuthorizationError.CodeNotFound);
124
+ });
125
+ });
@@ -0,0 +1,34 @@
1
+ import { type CoreIndex, type Segment, type SegmentIndex, type ServiceGas } from "#@typeberry/block";
2
+ import type { WorkPackageHash } from "#@typeberry/block/refine-context.js";
3
+ import type { WorkItem, WorkItemExtrinsic } from "#@typeberry/block/work-item.js";
4
+ import { WorkExecResult, WorkResult } from "#@typeberry/block/work-result.js";
5
+ import type { KnownSizeArray } from "#@typeberry/collections";
6
+ import type { ChainSpec, PvmBackend } from "#@typeberry/config";
7
+ import { type ReturnValue } from "#@typeberry/executor";
8
+ import { type Blake2b } from "#@typeberry/hash";
9
+ import type { State } from "#@typeberry/state";
10
+ export type RefineItemResult = {
11
+ result: WorkResult;
12
+ exports: readonly Segment[];
13
+ };
14
+ export type PerWorkItem<T> = KnownSizeArray<T, "for each work item">;
15
+ export type ImportedSegment = {
16
+ index: SegmentIndex;
17
+ data: Segment;
18
+ };
19
+ /**
20
+ * Refine PVM invocation (Psi_R).
21
+ *
22
+ * Executes a single work item's refinement logic.
23
+ */
24
+ export declare class Refine {
25
+ private readonly chainSpec;
26
+ private readonly pvmBackend;
27
+ private readonly blake2b;
28
+ constructor(chainSpec: ChainSpec, pvmBackend: PvmBackend, blake2b: Blake2b);
29
+ invoke(state: State, lookupState: State, idx: number, item: WorkItem, allImports: PerWorkItem<ImportedSegment[]>, allExtrinsics: PerWorkItem<WorkItemExtrinsic[]>, coreIndex: CoreIndex, workPackageHash: WorkPackageHash, exportOffset: number): Promise<RefineItemResult>;
30
+ static extractWorkResult(execResult: ReturnValue<ServiceGas>): WorkExecResult;
31
+ private getServiceCode;
32
+ private createRefineExternalities;
33
+ }
34
+ //# sourceMappingURL=refine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refine.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/in-core/refine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,KAAK,UAAU,EAIhB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAAE,cAAc,EAAsC,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAGjH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAA+D,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACpH,OAAO,EAAE,KAAK,OAAO,EAAa,MAAM,iBAAiB,CAAC;AAE1D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAK9C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;AAErE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAyBF;;;;GAIG;AACH,qBAAa,MAAM;IAEf,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAFP,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO;IAG7B,MAAM,CACV,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,KAAK,EAClB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,WAAW,CAAC,eAAe,EAAE,CAAC,EAC1C,aAAa,EAAE,WAAW,CAAC,iBAAiB,EAAE,CAAC,EAC/C,SAAS,EAAE,SAAS,EACpB,eAAe,EAAE,eAAe,EAChC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC;IA0F5B,MAAM,CAAC,iBAAiB,CAAC,UAAU,EAAE,WAAW,CAAC,UAAU,CAAC;IAiB5D,OAAO,CAAC,cAAc;IAyCtB,OAAO,CAAC,yBAAyB;CAsBlC"}