@typeberry/lib 0.5.10-6cb1bd5 → 0.5.10-ca4935b

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 (40) hide show
  1. package/package.json +1 -1
  2. package/packages/core/codec/encoder.d.ts +1 -1
  3. package/packages/core/codec/encoder.d.ts.map +1 -1
  4. package/packages/core/codec/encoder.js +3 -2
  5. package/packages/core/pvm-interface/pvm.d.ts +2 -0
  6. package/packages/core/pvm-interface/pvm.d.ts.map +1 -1
  7. package/packages/jam/executor/pvm-executor.d.ts +2 -2
  8. package/packages/jam/executor/pvm-executor.d.ts.map +1 -1
  9. package/packages/jam/in-core/externalities/refine.d.ts +14 -4
  10. package/packages/jam/in-core/externalities/refine.d.ts.map +1 -1
  11. package/packages/jam/in-core/externalities/refine.js +47 -3
  12. package/packages/jam/in-core/externalities/refine.test.js +49 -2
  13. package/packages/jam/in-core/in-core.d.ts.map +1 -1
  14. package/packages/jam/in-core/in-core.js +3 -5
  15. package/packages/jam/jam-host-calls/general/fetch.d.ts +159 -98
  16. package/packages/jam/jam-host-calls/general/fetch.d.ts.map +1 -1
  17. package/packages/jam/jam-host-calls/general/fetch.js +110 -16
  18. package/packages/jam/jam-host-calls/general/fetch.test.js +87 -56
  19. package/packages/jam/transition/accumulate/accumulate.js +2 -2
  20. package/packages/jam/transition/externalities/accumulate-fetch-externalities.d.ts +19 -0
  21. package/packages/jam/transition/externalities/accumulate-fetch-externalities.d.ts.map +1 -0
  22. package/packages/jam/transition/externalities/accumulate-fetch-externalities.js +45 -0
  23. package/packages/jam/transition/externalities/accumulate-fetch-externalities.test.d.ts +2 -0
  24. package/packages/jam/transition/externalities/accumulate-fetch-externalities.test.d.ts.map +1 -0
  25. package/packages/jam/transition/externalities/accumulate-fetch-externalities.test.js +192 -0
  26. package/packages/jam/transition/externalities/fetch-externalities.d.ts +3 -39
  27. package/packages/jam/transition/externalities/fetch-externalities.d.ts.map +1 -1
  28. package/packages/jam/transition/externalities/fetch-externalities.js +2 -88
  29. package/packages/jam/transition/externalities/index.d.ts +2 -0
  30. package/packages/jam/transition/externalities/index.d.ts.map +1 -1
  31. package/packages/jam/transition/externalities/index.js +2 -0
  32. package/packages/jam/transition/externalities/refine-fetch-externalities.d.ts +23 -0
  33. package/packages/jam/transition/externalities/refine-fetch-externalities.d.ts.map +1 -0
  34. package/packages/jam/transition/externalities/refine-fetch-externalities.js +48 -0
  35. package/packages/jam/transition/externalities/refine-fetch-externalities.test.d.ts +2 -0
  36. package/packages/jam/transition/externalities/refine-fetch-externalities.test.d.ts.map +1 -0
  37. package/packages/jam/transition/externalities/refine-fetch-externalities.test.js +32 -0
  38. package/packages/jam/transition/externalities/fetch-externalities.test.d.ts +0 -2
  39. package/packages/jam/transition/externalities/fetch-externalities.test.d.ts.map +0 -1
  40. package/packages/jam/transition/externalities/fetch-externalities.test.js +0 -254
@@ -1,14 +1,15 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, it } from "node:test";
3
3
  import { tryAsServiceId } from "#@typeberry/block";
4
- import { BytesBlob } from "#@typeberry/bytes";
4
+ import { Bytes, BytesBlob } from "#@typeberry/bytes";
5
+ import { HASH_SIZE } from "#@typeberry/hash";
5
6
  import { tryAsU64 } from "#@typeberry/numbers";
6
7
  import { HostCallMemory, HostCallRegisters, PvmExecution } from "#@typeberry/pvm-host-calls";
7
8
  import { tryAsGas } from "#@typeberry/pvm-interface";
8
9
  import { gasCounter, MemoryBuilder, tryAsMemoryIndex, tryAsSbrkIndex } from "#@typeberry/pvm-interpreter";
9
10
  import { PAGE_SIZE } from "#@typeberry/pvm-interpreter/memory/memory-consts.js";
10
11
  import { emptyRegistersBuffer } from "../utils.js";
11
- import { Fetch, FetchKind } from "./fetch.js";
12
+ import { Fetch, FetchContext, FetchKind } from "./fetch.js";
12
13
  import { HostCallResult } from "./results.js";
13
14
  describe("Fetch", () => {
14
15
  const IN_OUT_REG = 7;
@@ -16,7 +17,7 @@ describe("Fetch", () => {
16
17
  it("should return PvmExecution.Panic if memory write fails", async () => {
17
18
  const currentServiceId = tryAsServiceId(10_000);
18
19
  const blob = BytesBlob.blobFromNumbers([1, 2, 3]);
19
- const fetchMock = new FetchMock();
20
+ const fetchMock = new RefineFetchMock();
20
21
  fetchMock.constantsResponse = blob;
21
22
  const badOffset = tryAsU64(0xfffff);
22
23
  const registers = new HostCallRegisters(emptyRegistersBuffer());
@@ -33,10 +34,10 @@ describe("Fetch", () => {
33
34
  });
34
35
  it("should write empty result and set IN_OUT_REG to NONE if fetch returns null", async () => {
35
36
  const currentServiceId = tryAsServiceId(10_000);
36
- const fetchMock = new FetchMock();
37
- fetchMock.entropyResponse = null;
37
+ const fetchMock = new RefineFetchMock();
38
+ // authorizerTraceResponse is null by default — Kind 2 legitimately returns null
38
39
  const blob = BytesBlob.blobFromNumbers([]);
39
- const { registers, memory, readBack } = prepareRegsAndMemory(blob, FetchKind.Entropy);
40
+ const { registers, memory, readBack } = prepareRegsAndMemory(blob, FetchKind.AuthorizerTrace);
40
41
  const fetch = new Fetch(currentServiceId, fetchMock);
41
42
  const result = await fetch.execute(gas, registers, memory);
42
43
  assert.strictEqual(result, undefined);
@@ -47,7 +48,7 @@ describe("Fetch", () => {
47
48
  it("should write nothing if offset >= blob length", async () => {
48
49
  const currentServiceId = tryAsServiceId(10_000);
49
50
  const blob = BytesBlob.blobFromNumbers([1, 2, 3]);
50
- const fetchMock = new FetchMock();
51
+ const fetchMock = new RefineFetchMock();
51
52
  fetchMock.constantsResponse = blob;
52
53
  const { registers, memory, readBack } = prepareRegsAndMemory(blob, FetchKind.Constants, 5, 2);
53
54
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -59,7 +60,7 @@ describe("Fetch", () => {
59
60
  it("should clamp offset + length to blob end", async () => {
60
61
  const currentServiceId = tryAsServiceId(10_000);
61
62
  const blob = BytesBlob.blobFromNumbers([9, 8, 7, 6, 5]);
62
- const fetchMock = new FetchMock();
63
+ const fetchMock = new RefineFetchMock();
63
64
  fetchMock.constantsResponse = blob;
64
65
  const { registers, memory, readBack } = prepareRegsAndMemory(blob, FetchKind.Constants, 3, 10);
65
66
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -71,7 +72,7 @@ describe("Fetch", () => {
71
72
  it("should return NONE and write nothing if fetch kind is unknown", async () => {
72
73
  const currentServiceId = tryAsServiceId(10_000);
73
74
  const blob = BytesBlob.empty();
74
- const fetchMock = new FetchMock();
75
+ const fetchMock = new RefineFetchMock();
75
76
  fetchMock.constantsResponse = blob;
76
77
  const { registers, memory, readBack } = prepareRegsAndMemory(blob, FetchKind.Constants);
77
78
  registers.set(10, tryAsU64(999));
@@ -84,7 +85,7 @@ describe("Fetch", () => {
84
85
  it("should fetch constants and write result to memory", async () => {
85
86
  const currentServiceId = tryAsServiceId(10_000);
86
87
  const blob = BytesBlob.blobFromNumbers([1, 2, 3, 4, 5]);
87
- const fetchMock = new FetchMock();
88
+ const fetchMock = new RefineFetchMock();
88
89
  fetchMock.constantsResponse = blob;
89
90
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.Constants);
90
91
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -95,8 +96,8 @@ describe("Fetch", () => {
95
96
  });
96
97
  it("should fetch entropy and write result to memory", async () => {
97
98
  const currentServiceId = tryAsServiceId(10_000);
98
- const blob = BytesBlob.blobFromNumbers([10, 20, 30, 40]);
99
- const fetchMock = new FetchMock();
99
+ const blob = Bytes.fill(HASH_SIZE, 10).asOpaque();
100
+ const fetchMock = new RefineFetchMock();
100
101
  fetchMock.entropyResponse = blob;
101
102
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.Entropy);
102
103
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -108,7 +109,7 @@ describe("Fetch", () => {
108
109
  it("should fetch authorizer trace and write result to memory", async () => {
109
110
  const currentServiceId = tryAsServiceId(10_000);
110
111
  const blob = BytesBlob.blobFromNumbers([9, 9, 9]);
111
- const fetchMock = new FetchMock();
112
+ const fetchMock = new RefineFetchMock();
112
113
  fetchMock.authorizerTraceResponse = blob;
113
114
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.AuthorizerTrace);
114
115
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -120,7 +121,7 @@ describe("Fetch", () => {
120
121
  it("should fetch other work item extrinsics and write result to memory", async () => {
121
122
  const currentServiceId = tryAsServiceId(10_000);
122
123
  const blob = BytesBlob.blobFromNumbers([42, 43, 44]);
123
- const fetchMock = new FetchMock();
124
+ const fetchMock = new RefineFetchMock();
124
125
  const workItem = tryAsU64(123);
125
126
  const index = tryAsU64(7);
126
127
  const key = `${workItem}:${index}`;
@@ -138,7 +139,7 @@ describe("Fetch", () => {
138
139
  it("should fetch my extrinsics and write result to memory", async () => {
139
140
  const currentServiceId = tryAsServiceId(10_000);
140
141
  const blob = BytesBlob.blobFromNumbers([11, 12, 13]);
141
- const fetchMock = new FetchMock();
142
+ const fetchMock = new RefineFetchMock();
142
143
  const index = tryAsU64(5);
143
144
  const key = `null:${index}`;
144
145
  fetchMock.workItemExtrinsicResponses.set(key, blob);
@@ -154,7 +155,7 @@ describe("Fetch", () => {
154
155
  it("should fetch other work item imports and write result to memory", async () => {
155
156
  const currentServiceId = tryAsServiceId(10_000);
156
157
  const blob = BytesBlob.blobFromNumbers([21, 22, 23]);
157
- const fetchMock = new FetchMock();
158
+ const fetchMock = new RefineFetchMock();
158
159
  const workItem = tryAsU64(42);
159
160
  const index = tryAsU64(3);
160
161
  const key = `${workItem}:${index}`;
@@ -172,7 +173,7 @@ describe("Fetch", () => {
172
173
  it("should fetch my imports and write result to memory", async () => {
173
174
  const currentServiceId = tryAsServiceId(10_000);
174
175
  const blob = BytesBlob.blobFromNumbers([31, 32, 33]);
175
- const fetchMock = new FetchMock();
176
+ const fetchMock = new RefineFetchMock();
176
177
  const index = tryAsU64(8);
177
178
  const key = `null:${index}`;
178
179
  fetchMock.workItemImportResponses.set(key, blob);
@@ -188,7 +189,7 @@ describe("Fetch", () => {
188
189
  it("should fetch work package and write result to memory", async () => {
189
190
  const currentServiceId = tryAsServiceId(10_000);
190
191
  const blob = BytesBlob.blobFromNumbers([100, 101, 102]);
191
- const fetchMock = new FetchMock();
192
+ const fetchMock = new RefineFetchMock();
192
193
  fetchMock.workPackageResponse = blob;
193
194
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.WorkPackage);
194
195
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -200,7 +201,7 @@ describe("Fetch", () => {
200
201
  it("should fetch authorizer and write result to memory", async () => {
201
202
  const currentServiceId = tryAsServiceId(10_000);
202
203
  const blob = BytesBlob.blobFromNumbers([201, 202, 203]);
203
- const fetchMock = new FetchMock();
204
+ const fetchMock = new RefineFetchMock();
204
205
  fetchMock.authorizerResponse = blob;
205
206
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.Authorizer);
206
207
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -212,7 +213,7 @@ describe("Fetch", () => {
212
213
  it("should fetch authorization token and write result to memory", async () => {
213
214
  const currentServiceId = tryAsServiceId(10_000);
214
215
  const blob = BytesBlob.blobFromNumbers([210, 211, 212]);
215
- const fetchMock = new FetchMock();
216
+ const fetchMock = new RefineFetchMock();
216
217
  fetchMock.authorizationTokenResponse = blob;
217
218
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.AuthorizationToken);
218
219
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -224,7 +225,7 @@ describe("Fetch", () => {
224
225
  it("should fetch refine context and write result to memory", async () => {
225
226
  const currentServiceId = tryAsServiceId(10_000);
226
227
  const blob = BytesBlob.blobFromNumbers([88, 89, 90]);
227
- const fetchMock = new FetchMock();
228
+ const fetchMock = new RefineFetchMock();
228
229
  fetchMock.refineContextResponse = blob;
229
230
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.RefineContext);
230
231
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -236,7 +237,7 @@ describe("Fetch", () => {
236
237
  it("should fetch all work items and write result to memory", async () => {
237
238
  const currentServiceId = tryAsServiceId(10_000);
238
239
  const blob = BytesBlob.blobFromNumbers([70, 71, 72]);
239
- const fetchMock = new FetchMock();
240
+ const fetchMock = new RefineFetchMock();
240
241
  fetchMock.allWorkItemsResponse = blob;
241
242
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.AllWorkItems);
242
243
  const fetch = new Fetch(currentServiceId, fetchMock);
@@ -248,7 +249,7 @@ describe("Fetch", () => {
248
249
  it("should fetch one work item and write result to memory", async () => {
249
250
  const currentServiceId = tryAsServiceId(10_000);
250
251
  const blob = BytesBlob.blobFromNumbers([33, 34, 35]);
251
- const fetchMock = new FetchMock();
252
+ const fetchMock = new RefineFetchMock();
252
253
  const workItem = tryAsU64(55);
253
254
  fetchMock.oneWorkItemResponses.set(workItem.toString(), blob);
254
255
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.OneWorkItem);
@@ -263,7 +264,7 @@ describe("Fetch", () => {
263
264
  it("should fetch work item payload and write result to memory", async () => {
264
265
  const currentServiceId = tryAsServiceId(10_000);
265
266
  const blob = BytesBlob.blobFromNumbers([60, 61, 62]);
266
- const fetchMock = new FetchMock();
267
+ const fetchMock = new RefineFetchMock();
267
268
  const workItem = tryAsU64(77);
268
269
  fetchMock.workItemPayloadResponses.set(workItem.toString(), blob);
269
270
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.WorkItemPayload);
@@ -278,8 +279,8 @@ describe("Fetch", () => {
278
279
  it("should fetch all transfers and operands and write result to memory", async () => {
279
280
  const currentServiceId = tryAsServiceId(10_000);
280
281
  const blob = BytesBlob.blobFromNumbers([101, 102, 103]);
281
- const fetchMock = new FetchMock();
282
- fetchMock.allTransfersAndOperandsResponses = blob;
282
+ const fetchMock = new AccumulateFetchMock();
283
+ fetchMock.allTransfersAndOperandsResponse = blob;
283
284
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.AllTransfersAndOperands);
284
285
  const fetch = new Fetch(currentServiceId, fetchMock);
285
286
  const result = await fetch.execute(gas, registers, memory);
@@ -290,7 +291,7 @@ describe("Fetch", () => {
290
291
  it("should fetch one operand or transfer and write result to memory", async () => {
291
292
  const currentServiceId = tryAsServiceId(10_000);
292
293
  const blob = BytesBlob.blobFromNumbers([115, 116, 117]);
293
- const fetchMock = new FetchMock();
294
+ const fetchMock = new AccumulateFetchMock();
294
295
  const index = tryAsU64(9);
295
296
  fetchMock.oneTransferOrOperandResponses.set(index.toString(), blob);
296
297
  const { registers, memory, readBack, expectedLength } = prepareRegsAndMemory(blob, FetchKind.OneTransferOrOperand);
@@ -302,6 +303,43 @@ describe("Fetch", () => {
302
303
  assert.deepStrictEqual(readBack(), blob.raw);
303
304
  assert.deepStrictEqual(fetchMock.oneTransferOrOperandData, [[index]]);
304
305
  });
306
+ it("should return NONE for refine-only kinds in accumulate context", async () => {
307
+ const currentServiceId = tryAsServiceId(10_000);
308
+ const fetchMock = new AccumulateFetchMock();
309
+ const blob = BytesBlob.empty();
310
+ for (const kind of [
311
+ FetchKind.AuthorizerTrace,
312
+ FetchKind.OtherWorkItemExtrinsics,
313
+ FetchKind.MyExtrinsics,
314
+ FetchKind.OtherWorkItemImports,
315
+ FetchKind.MyImports,
316
+ FetchKind.WorkPackage,
317
+ FetchKind.Authorizer,
318
+ FetchKind.AuthorizationToken,
319
+ FetchKind.RefineContext,
320
+ FetchKind.AllWorkItems,
321
+ FetchKind.OneWorkItem,
322
+ FetchKind.WorkItemPayload,
323
+ ]) {
324
+ const { registers, memory } = prepareRegsAndMemory(blob, kind);
325
+ const fetch = new Fetch(currentServiceId, fetchMock);
326
+ const result = await fetch.execute(gas, registers, memory);
327
+ assert.strictEqual(result, undefined, `Expected undefined for kind ${kind}`);
328
+ assert.strictEqual(registers.get(IN_OUT_REG), HostCallResult.NONE, `Expected NONE for kind ${kind}`);
329
+ }
330
+ });
331
+ it("should return NONE for accumulate-only kinds in refine context", async () => {
332
+ const currentServiceId = tryAsServiceId(10_000);
333
+ const fetchMock = new RefineFetchMock();
334
+ const blob = BytesBlob.empty();
335
+ for (const kind of [FetchKind.AllTransfersAndOperands, FetchKind.OneTransferOrOperand]) {
336
+ const { registers, memory } = prepareRegsAndMemory(blob, kind);
337
+ const fetch = new Fetch(currentServiceId, fetchMock);
338
+ const result = await fetch.execute(gas, registers, memory);
339
+ assert.strictEqual(result, undefined, `Expected undefined for kind ${kind}`);
340
+ assert.strictEqual(registers.get(IN_OUT_REG), HostCallResult.NONE, `Expected NONE for kind ${kind}`);
341
+ }
342
+ });
305
343
  function prepareRegsAndMemory(blob, fetchKind, offset = 0, length = blob.length) {
306
344
  const pageStart = 2 ** 16;
307
345
  const memOffset = tryAsU64(pageStart + 1234);
@@ -327,14 +365,12 @@ describe("Fetch", () => {
327
365
  };
328
366
  }
329
367
  });
330
- class FetchMock {
368
+ class RefineFetchMock {
369
+ context = FetchContext.Refine;
331
370
  workItemExtrinsicData = [];
332
371
  workItemImportData = [];
333
372
  oneWorkItemData = [];
334
373
  workItemPayloadData = [];
335
- oneOperandData = [];
336
- oneTransferData = [];
337
- oneTransferOrOperandData = [];
338
374
  constantsResponse = null;
339
375
  entropyResponse = null;
340
376
  authorizerTraceResponse = null;
@@ -347,12 +383,6 @@ class FetchMock {
347
383
  allWorkItemsResponse = null;
348
384
  oneWorkItemResponses = new Map();
349
385
  workItemPayloadResponses = new Map();
350
- allOperandsResponse = null;
351
- oneOperandResponses = new Map();
352
- allTransfersResponse = null;
353
- oneTransferResponses = new Map();
354
- allTransfersAndOperandsResponses = null;
355
- oneTransferOrOperandResponses = new Map();
356
386
  constants() {
357
387
  if (this.constantsResponse === null) {
358
388
  throw new Error("Unexpected call to constants.");
@@ -360,6 +390,9 @@ class FetchMock {
360
390
  return this.constantsResponse;
361
391
  }
362
392
  entropy() {
393
+ if (this.entropyResponse === null) {
394
+ throw new Error("Unexpected call to entropy.");
395
+ }
363
396
  return this.entropyResponse;
364
397
  }
365
398
  authorizerTrace() {
@@ -412,30 +445,28 @@ class FetchMock {
412
445
  }
413
446
  return this.workItemPayloadResponses.get(key) ?? null;
414
447
  }
415
- allOperands() {
416
- return this.allOperandsResponse;
417
- }
418
- oneOperand(operandIndex) {
419
- this.oneOperandData.push([operandIndex]);
420
- const key = operandIndex.toString();
421
- if (!this.oneOperandResponses.has(key)) {
422
- throw new Error(`Missing mock response for oneOperand(${key})`);
448
+ }
449
+ class AccumulateFetchMock {
450
+ context = FetchContext.Accumulate;
451
+ oneTransferOrOperandData = [];
452
+ constantsResponse = null;
453
+ entropyResponse = null;
454
+ allTransfersAndOperandsResponse = null;
455
+ oneTransferOrOperandResponses = new Map();
456
+ constants() {
457
+ if (this.constantsResponse === null) {
458
+ throw new Error("Unexpected call to constants.");
423
459
  }
424
- return this.oneOperandResponses.get(key) ?? null;
425
- }
426
- allTransfers() {
427
- return this.allTransfersResponse;
460
+ return this.constantsResponse;
428
461
  }
429
- oneTransfer(transferIndex) {
430
- this.oneTransferData.push([transferIndex]);
431
- const key = transferIndex.toString();
432
- if (!this.oneTransferResponses.has(key)) {
433
- throw new Error(`Missing mock response for oneTransfer(${key})`);
462
+ entropy() {
463
+ if (this.entropyResponse === null) {
464
+ throw new Error("Unexpected call to entropy.");
434
465
  }
435
- return this.oneTransferResponses.get(key) ?? null;
466
+ return this.entropyResponse;
436
467
  }
437
468
  allTransfersAndOperands() {
438
- return this.allTransfersAndOperandsResponses;
469
+ return this.allTransfersAndOperandsResponse;
439
470
  }
440
471
  oneTransferOrOperand(index) {
441
472
  this.oneTransferOrOperandData.push([index]);
@@ -11,7 +11,7 @@ import { MAX_VALUE_U64, sumU64, tryAsU32 } from "#@typeberry/numbers";
11
11
  import { accumulationOutputComparator, hashComparator, ServiceAccountInfo, tryAsPerCore, } from "#@typeberry/state";
12
12
  import { assertEmpty, Compatibility, GpVersion, Result, TestSuite } from "#@typeberry/utils";
13
13
  import { AccumulateExternalities } from "../externalities/accumulate-externalities.js";
14
- import { FetchExternalities } from "../externalities/index.js";
14
+ import { AccumulateFetchExternalities } from "../externalities/accumulate-fetch-externalities.js";
15
15
  import { AccumulateData } from "./accumulate-data.js";
16
16
  import { AccumulateQueue, pruneQueue } from "./accumulate-queue.js";
17
17
  import { GAS_TO_INVOKE_WORK_REPORT, } from "./accumulate-state.js";
@@ -86,7 +86,7 @@ export class Accumulate {
86
86
  }
87
87
  const nextServiceId = generateNextServiceId({ serviceId, entropy, timeslot: slot }, this.chainSpec, this.blake2b);
88
88
  const partialState = new AccumulateExternalities(this.chainSpec, this.blake2b, updatedState, serviceId, nextServiceId, slot);
89
- const fetchExternalities = FetchExternalities.createForAccumulate({ entropy, transfers, operands }, this.chainSpec);
89
+ const fetchExternalities = new AccumulateFetchExternalities(entropy, transfers, operands, this.chainSpec);
90
90
  const externalities = {
91
91
  partialState,
92
92
  serviceExternalities: partialState,
@@ -0,0 +1,19 @@
1
+ import type { EntropyHash } from "#@typeberry/block";
2
+ import type { BytesBlob } from "#@typeberry/bytes";
3
+ import type { ChainSpec } from "#@typeberry/config";
4
+ import { general, type PendingTransfer } from "#@typeberry/jam-host-calls";
5
+ import type { U64 } from "#@typeberry/numbers";
6
+ import type { Operand } from "../accumulate/operand.js";
7
+ export declare class AccumulateFetchExternalities implements general.IAccumulateFetch {
8
+ private readonly entropyHash;
9
+ private readonly transfers;
10
+ private readonly operands;
11
+ private readonly chainSpec;
12
+ readonly context = general.FetchContext.Accumulate;
13
+ constructor(entropyHash: EntropyHash, transfers: PendingTransfer[], operands: Operand[], chainSpec: ChainSpec);
14
+ constants(): BytesBlob;
15
+ entropy(): EntropyHash;
16
+ allTransfersAndOperands(): BytesBlob | null;
17
+ oneTransferOrOperand(index: U64): BytesBlob | null;
18
+ }
19
+ //# sourceMappingURL=accumulate-fetch-externalities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accumulate-fetch-externalities.d.ts","sourceRoot":"","sources":["../../../../../../packages/jam/transition/externalities/accumulate-fetch-externalities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AASxD,qBAAa,4BAA6B,YAAW,OAAO,CAAC,gBAAgB;IAIzE,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAN5B,QAAQ,CAAC,OAAO,mCAAmC;gBAGhC,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,eAAe,EAAE,EAC5B,QAAQ,EAAE,OAAO,EAAE,EACnB,SAAS,EAAE,SAAS;IAGvC,SAAS,IAAI,SAAS;IAItB,OAAO,IAAI,WAAW;IAItB,uBAAuB,IAAI,SAAS,GAAG,IAAI;IAU3C,oBAAoB,CAAC,KAAK,EAAE,GAAG,GAAG,SAAS,GAAG,IAAI;CAqBnD"}
@@ -0,0 +1,45 @@
1
+ import { Encoder } from "#@typeberry/codec";
2
+ import { general } from "#@typeberry/jam-host-calls";
3
+ import { getEncodedConstants, TRANSFER_OR_OPERAND, TRANSFERS_AND_OPERANDS, TransferOperandKind, } from "./fetch-externalities.js";
4
+ export class AccumulateFetchExternalities {
5
+ entropyHash;
6
+ transfers;
7
+ operands;
8
+ chainSpec;
9
+ context = general.FetchContext.Accumulate;
10
+ constructor(entropyHash, transfers, operands, chainSpec) {
11
+ this.entropyHash = entropyHash;
12
+ this.transfers = transfers;
13
+ this.operands = operands;
14
+ this.chainSpec = chainSpec;
15
+ }
16
+ constants() {
17
+ return getEncodedConstants(this.chainSpec);
18
+ }
19
+ entropy() {
20
+ return this.entropyHash;
21
+ }
22
+ allTransfersAndOperands() {
23
+ const transfersAndOperands = this.transfers
24
+ .map((transfer) => ({ kind: TransferOperandKind.TRANSFER, value: transfer }))
25
+ .concat(this.operands.map((operand) => ({ kind: TransferOperandKind.OPERAND, value: operand })));
26
+ return Encoder.encodeObject(TRANSFERS_AND_OPERANDS, transfersAndOperands, this.chainSpec);
27
+ }
28
+ oneTransferOrOperand(index) {
29
+ if (index >= this.transfers.length + this.operands.length) {
30
+ return null;
31
+ }
32
+ // Transfers-first ordering, consistent with allTransfersAndOperands()
33
+ const kind = index < this.transfers.length ? TransferOperandKind.TRANSFER : TransferOperandKind.OPERAND;
34
+ const transferOrOperand = kind === TransferOperandKind.TRANSFER
35
+ ? { kind: TransferOperandKind.TRANSFER, value: this.transfers[Number(index)] }
36
+ : {
37
+ kind: TransferOperandKind.OPERAND,
38
+ value: this.operands[Number(index) - this.transfers.length],
39
+ };
40
+ if (transferOrOperand.value === undefined) {
41
+ return null;
42
+ }
43
+ return Encoder.encodeObject(TRANSFER_OR_OPERAND, transferOrOperand, this.chainSpec);
44
+ }
45
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=accumulate-fetch-externalities.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accumulate-fetch-externalities.test.d.ts","sourceRoot":"","sources":["../../../../../../packages/jam/transition/externalities/accumulate-fetch-externalities.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,192 @@
1
+ import assert from "node:assert";
2
+ import { describe, it } from "node:test";
3
+ import { tryAsServiceGas, tryAsServiceId } from "#@typeberry/block";
4
+ import { WorkExecResult } from "#@typeberry/block/work-result.js";
5
+ import { Bytes, BytesBlob } from "#@typeberry/bytes";
6
+ import { codec, Encoder } from "#@typeberry/codec";
7
+ import { fullChainSpec, tinyChainSpec } from "#@typeberry/config";
8
+ import { HASH_SIZE } from "#@typeberry/hash";
9
+ import { TRANSFER_MEMO_BYTES } from "#@typeberry/jam-host-calls/externalities/partial-state.js";
10
+ import { PendingTransfer } from "#@typeberry/jam-host-calls/externalities/pending-transfer.js";
11
+ import { tryAsU64 } from "#@typeberry/numbers";
12
+ import { Operand } from "../accumulate/operand.js";
13
+ import { AccumulateFetchExternalities } from "./accumulate-fetch-externalities.js";
14
+ import { TRANSFER_OR_OPERAND, TransferOperandKind } from "./fetch-externalities.js";
15
+ describe("AccumulateFetchExternalities", () => {
16
+ const prepareOperands = (length) => {
17
+ const operands = [];
18
+ for (let i = 0; i < length; i++) {
19
+ operands.push(Operand.create({
20
+ authorizationOutput: BytesBlob.empty(),
21
+ authorizerHash: Bytes.fill(HASH_SIZE, i + 1).asOpaque(),
22
+ exportsRoot: Bytes.fill(HASH_SIZE, i + 2).asOpaque(),
23
+ hash: Bytes.fill(HASH_SIZE, i + 4).asOpaque(),
24
+ payloadHash: Bytes.fill(HASH_SIZE, i + 5).asOpaque(),
25
+ result: WorkExecResult.ok(BytesBlob.empty()),
26
+ gas: tryAsServiceGas(1_000),
27
+ }));
28
+ }
29
+ return operands;
30
+ };
31
+ const prepareTransfers = (length) => {
32
+ const transfers = [];
33
+ for (let i = 0; i < length; i++) {
34
+ transfers.push(PendingTransfer.create({
35
+ amount: tryAsU64(1000),
36
+ source: tryAsServiceId(i),
37
+ destination: tryAsServiceId(i + 1),
38
+ gas: tryAsServiceGas(10),
39
+ memo: Bytes.fill(TRANSFER_MEMO_BYTES, 0),
40
+ }));
41
+ }
42
+ return transfers;
43
+ };
44
+ // allTransfersAndOperands: transfers first, then operands
45
+ const toAllTransfersAndOperands = (operands, transfers) => {
46
+ return [
47
+ ...transfers.map((t) => ({ kind: TransferOperandKind.TRANSFER, value: t })),
48
+ ...operands.map((o) => ({ kind: TransferOperandKind.OPERAND, value: o })),
49
+ ];
50
+ };
51
+ // oneTransferOrOperand: transfers first, then operands (same as allTransfersAndOperands)
52
+ const toOneTransferOrOperandAt = (operands, transfers, index) => {
53
+ if (index >= transfers.length + operands.length) {
54
+ return null;
55
+ }
56
+ if (index < transfers.length) {
57
+ return { kind: TransferOperandKind.TRANSFER, value: transfers[index] };
58
+ }
59
+ return { kind: TransferOperandKind.OPERAND, value: operands[index - transfers.length] };
60
+ };
61
+ const encodeOneTransferOrOperand = (item, chainSpec) => {
62
+ if (item === null) {
63
+ return null;
64
+ }
65
+ return Encoder.encodeObject(TRANSFER_OR_OPERAND, item, chainSpec);
66
+ };
67
+ const prepareAccumulateData = ({ chainSpec, operands, entropy, transfers, }) => {
68
+ const defaultChainSpec = tinyChainSpec;
69
+ const defaultEntropy = Bytes.zero(HASH_SIZE).asOpaque();
70
+ const defaultOperands = [];
71
+ const defaultTransfers = [];
72
+ return new AccumulateFetchExternalities(entropy ?? defaultEntropy, transfers ?? defaultTransfers, operands ?? defaultOperands, chainSpec ?? defaultChainSpec);
73
+ };
74
+ it("should return different constants for different chain specs", () => {
75
+ const tinyFetchExternalities = prepareAccumulateData({ chainSpec: tinyChainSpec });
76
+ const fullFetchExternalities = prepareAccumulateData({ chainSpec: fullChainSpec });
77
+ const tinyConstants = tinyFetchExternalities.constants();
78
+ const fullConstants = fullFetchExternalities.constants();
79
+ assert.notStrictEqual(tinyConstants.length, 0);
80
+ assert.notStrictEqual(fullConstants.length, 0);
81
+ assert.notDeepStrictEqual(tinyConstants, fullConstants);
82
+ });
83
+ it("should return entropy hash", () => {
84
+ const expectedEntropy = Bytes.fill(HASH_SIZE, 5).asOpaque();
85
+ const fetchExternalities = prepareAccumulateData({ entropy: expectedEntropy });
86
+ const entropy = fetchExternalities.entropy();
87
+ assert.deepStrictEqual(entropy, expectedEntropy);
88
+ });
89
+ it("should return all transfers and operands", () => {
90
+ const operands = prepareOperands(3);
91
+ const transfers = prepareTransfers(2);
92
+ const chainSpec = tinyChainSpec;
93
+ const expected = toAllTransfersAndOperands(operands, transfers);
94
+ const encodedExpected = Encoder.encodeObject(codec.sequenceVarLen(TRANSFER_OR_OPERAND), expected, chainSpec);
95
+ const fetchExternalities = prepareAccumulateData({ operands, transfers, chainSpec });
96
+ const result = fetchExternalities.allTransfersAndOperands();
97
+ assert.deepStrictEqual(result, encodedExpected);
98
+ });
99
+ it("should return empty encoded sequence when no transfers and no operands", () => {
100
+ const chainSpec = tinyChainSpec;
101
+ const encodedExpected = Encoder.encodeObject(codec.sequenceVarLen(TRANSFER_OR_OPERAND), [], chainSpec);
102
+ const fetchExternalities = prepareAccumulateData({ operands: [], transfers: [], chainSpec });
103
+ const result = fetchExternalities.allTransfersAndOperands();
104
+ assert.deepStrictEqual(result, encodedExpected);
105
+ });
106
+ it("should return one transfer by index (first range)", () => {
107
+ const operands = prepareOperands(3);
108
+ const transfers = prepareTransfers(2);
109
+ const chainSpec = tinyChainSpec;
110
+ const encodedExpected = encodeOneTransferOrOperand(toOneTransferOrOperandAt(operands, transfers, 0), chainSpec);
111
+ const fetchExternalities = prepareAccumulateData({ operands, transfers, chainSpec });
112
+ // Transfers come first (indices 0..1), then operands (indices 2..4)
113
+ const result = fetchExternalities.oneTransferOrOperand(tryAsU64(0));
114
+ assert.deepStrictEqual(result, encodedExpected);
115
+ });
116
+ it("should return one operand by index (second range)", () => {
117
+ const operands = prepareOperands(3);
118
+ const transfers = prepareTransfers(2);
119
+ const chainSpec = tinyChainSpec;
120
+ const encodedExpected = encodeOneTransferOrOperand(toOneTransferOrOperandAt(operands, transfers, 2), chainSpec);
121
+ const fetchExternalities = prepareAccumulateData({ operands, transfers, chainSpec });
122
+ // Operands start after transfers, so index 2 is the first operand
123
+ const result = fetchExternalities.oneTransferOrOperand(tryAsU64(2));
124
+ assert.deepStrictEqual(result, encodedExpected);
125
+ });
126
+ it("should return null when index is out of bounds", () => {
127
+ const operands = prepareOperands(3);
128
+ const transfers = prepareTransfers(2);
129
+ const chainSpec = tinyChainSpec;
130
+ const fetchExternalities = prepareAccumulateData({ operands, transfers, chainSpec });
131
+ // Total items: 3 operands + 2 transfers = 5, so index 5 is out of bounds
132
+ const result = fetchExternalities.oneTransferOrOperand(tryAsU64(5));
133
+ assert.strictEqual(result, null);
134
+ });
135
+ it("should return null when index is far out of bounds", () => {
136
+ const operands = prepareOperands(3);
137
+ const transfers = prepareTransfers(2);
138
+ const chainSpec = tinyChainSpec;
139
+ const fetchExternalities = prepareAccumulateData({ operands, transfers, chainSpec });
140
+ const result = fetchExternalities.oneTransferOrOperand(tryAsU64(153));
141
+ assert.strictEqual(result, null);
142
+ });
143
+ it("should have consistent encoding between all and one", () => {
144
+ const operands = prepareOperands(2);
145
+ const transfers = prepareTransfers(2);
146
+ const chainSpec = tinyChainSpec;
147
+ const allItems = toAllTransfersAndOperands(operands, transfers);
148
+ const encodedAll = Encoder.encodeObject(codec.sequenceVarLen(TRANSFER_OR_OPERAND), allItems, chainSpec);
149
+ const fetchExternalities = prepareAccumulateData({ operands, transfers, chainSpec });
150
+ const all = fetchExternalities.allTransfersAndOperands();
151
+ assert.deepStrictEqual(all, encodedAll);
152
+ for (let i = 0; i < operands.length + transfers.length; i++) {
153
+ const one = fetchExternalities.oneTransferOrOperand(tryAsU64(i));
154
+ const encodedOne = encodeOneTransferOrOperand(toOneTransferOrOperandAt(operands, transfers, i), chainSpec);
155
+ assert.deepStrictEqual(one, encodedOne, `Mismatch at index ${i}`);
156
+ }
157
+ const outOfRange = fetchExternalities.oneTransferOrOperand(tryAsU64(operands.length + transfers.length));
158
+ assert.strictEqual(outOfRange, null);
159
+ });
160
+ it("should handle only operands without transfers", () => {
161
+ const operands = prepareOperands(5);
162
+ const chainSpec = tinyChainSpec;
163
+ const allItems = toAllTransfersAndOperands(operands, []);
164
+ const encodedAll = Encoder.encodeObject(codec.sequenceVarLen(TRANSFER_OR_OPERAND), allItems, chainSpec);
165
+ const fetchExternalities = prepareAccumulateData({ operands, transfers: [], chainSpec });
166
+ const result = fetchExternalities.allTransfersAndOperands();
167
+ assert.deepStrictEqual(result, encodedAll);
168
+ for (let i = 0; i < operands.length; i++) {
169
+ const one = fetchExternalities.oneTransferOrOperand(tryAsU64(i));
170
+ const encodedOne = encodeOneTransferOrOperand(toOneTransferOrOperandAt(operands, [], i), chainSpec);
171
+ assert.deepStrictEqual(one, encodedOne, `Mismatch at operand index ${i}`);
172
+ }
173
+ const outOfRange = fetchExternalities.oneTransferOrOperand(tryAsU64(operands.length));
174
+ assert.strictEqual(outOfRange, null);
175
+ });
176
+ it("should handle only transfers without operands", () => {
177
+ const transfers = prepareTransfers(5);
178
+ const chainSpec = tinyChainSpec;
179
+ const allItems = toAllTransfersAndOperands([], transfers);
180
+ const encodedAll = Encoder.encodeObject(codec.sequenceVarLen(TRANSFER_OR_OPERAND), allItems, chainSpec);
181
+ const fetchExternalities = prepareAccumulateData({ operands: [], transfers, chainSpec });
182
+ const result = fetchExternalities.allTransfersAndOperands();
183
+ assert.deepStrictEqual(result, encodedAll);
184
+ for (let i = 0; i < transfers.length; i++) {
185
+ const one = fetchExternalities.oneTransferOrOperand(tryAsU64(i));
186
+ const encodedOne = encodeOneTransferOrOperand(toOneTransferOrOperandAt([], transfers, i), chainSpec);
187
+ assert.deepStrictEqual(one, encodedOne, `Mismatch at transfer index ${i}`);
188
+ }
189
+ const outOfRange = fetchExternalities.oneTransferOrOperand(tryAsU64(transfers.length));
190
+ assert.strictEqual(outOfRange, null);
191
+ });
192
+ });