@typeberry/lib 0.5.7-a809a5e → 0.5.7-d496f70

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 (41) hide show
  1. package/package.json +3 -1
  2. package/packages/jam/jamnp-s/network.d.ts +2 -0
  3. package/packages/jam/jamnp-s/network.d.ts.map +1 -1
  4. package/packages/jam/jamnp-s/network.js +4 -0
  5. package/packages/jam/jamnp-s/protocol/ce-131-ce-132-safrole-ticket-distribution.d.ts +4 -0
  6. package/packages/jam/jamnp-s/protocol/ce-131-ce-132-safrole-ticket-distribution.d.ts.map +1 -1
  7. package/packages/jam/jamnp-s/protocol/ce-131-ce-132-safrole-ticket-distribution.js +4 -0
  8. package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts +33 -0
  9. package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts.map +1 -0
  10. package/packages/jam/jamnp-s/tasks/ticket-distribution.js +115 -0
  11. package/packages/jam/jamnp-s/tasks/ticket-distribution.test.d.ts +2 -0
  12. package/packages/jam/jamnp-s/tasks/ticket-distribution.test.d.ts.map +1 -0
  13. package/packages/jam/jamnp-s/tasks/ticket-distribution.test.js +219 -0
  14. package/packages/jam/node/main.d.ts.map +1 -1
  15. package/packages/jam/node/main.js +56 -26
  16. package/packages/jam/node/workers.d.ts +10 -3
  17. package/packages/jam/node/workers.d.ts.map +1 -1
  18. package/packages/jam/node/workers.js +16 -7
  19. package/packages/workers/api-node/config.d.ts +5 -1
  20. package/packages/workers/api-node/config.d.ts.map +1 -1
  21. package/packages/workers/api-node/config.js +9 -3
  22. package/packages/workers/api-node/port.d.ts +8 -0
  23. package/packages/workers/api-node/port.d.ts.map +1 -1
  24. package/packages/workers/api-node/port.js +10 -0
  25. package/packages/workers/block-authorship/bootstrap-main.js +10 -3
  26. package/packages/workers/block-authorship/main.d.ts +2 -1
  27. package/packages/workers/block-authorship/main.d.ts.map +1 -1
  28. package/packages/workers/block-authorship/main.js +4 -3
  29. package/packages/workers/comms-authorship-network/index.d.ts +3 -0
  30. package/packages/workers/comms-authorship-network/index.d.ts.map +1 -0
  31. package/packages/workers/comms-authorship-network/index.js +2 -0
  32. package/packages/workers/comms-authorship-network/protocol.d.ts +27 -0
  33. package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -0
  34. package/packages/workers/comms-authorship-network/protocol.js +24 -0
  35. package/packages/workers/comms-authorship-network/tickets-message.d.ts +18 -0
  36. package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -0
  37. package/packages/workers/comms-authorship-network/tickets-message.js +19 -0
  38. package/packages/workers/jam-network/bootstrap-main.js +10 -3
  39. package/packages/workers/jam-network/main.d.ts +2 -1
  40. package/packages/workers/jam-network/main.d.ts.map +1 -1
  41. package/packages/workers/jam-network/main.js +8 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typeberry/lib",
3
- "version": "0.5.7-a809a5e",
3
+ "version": "0.5.7-d496f70",
4
4
  "description": "Typeberry Library",
5
5
  "main": "./bin/lib/index.js",
6
6
  "types": "./bin/lib/index.d.ts",
@@ -256,6 +256,8 @@
256
256
  "#@typeberry/workers-api-node/*": "./packages/workers/api-node/*",
257
257
  "#@typeberry/block-authorship": "./packages/workers/block-authorship/index.js",
258
258
  "#@typeberry/block-authorship/*": "./packages/workers/block-authorship/*",
259
+ "#@typeberry/comms-authorship-network": "./packages/workers/comms-authorship-network/index.js",
260
+ "#@typeberry/comms-authorship-network/*": "./packages/workers/comms-authorship-network/*",
259
261
  "#@typeberry/importer": "./packages/workers/importer/index.js",
260
262
  "#@typeberry/importer/*": "./packages/workers/importer/*",
261
263
  "#@typeberry/jam-network": "./packages/workers/jam-network/index.js",
@@ -6,12 +6,14 @@ import { type Network, type Peer } from "#@typeberry/networking";
6
6
  import { type Bootnode } from "./peers.js";
7
7
  import { StreamManager } from "./stream-manager.js";
8
8
  import { SyncTask } from "./tasks/sync.js";
9
+ import { TicketDistributionTask } from "./tasks/ticket-distribution.js";
9
10
  export declare function setup(bind: {
10
11
  host: string;
11
12
  port: number;
12
13
  }, genesisHash: HeaderHash, key: ed25519.Ed25519Pair, bootnodes: Bootnode[], spec: ChainSpec, blocks: BlocksDb, onNewBlocks: (blocks: BlockView[]) => Promise<void>): Promise<{
13
14
  network: import("@typeberry/networking/quic-network.js").QuicNetwork;
14
15
  syncTask: SyncTask;
16
+ ticketTask: TicketDistributionTask;
15
17
  streamManager: StreamManager;
16
18
  }>;
17
19
  export declare function setupPeerListeners(syncTask: SyncTask, network: Network<Peer>, streamManager: StreamManager): void;
@@ -1 +1 @@
1
- {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/jamnp-s/network.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAQ,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAK3C,wBAAsB,KAAK,CACzB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACpC,WAAW,EAAE,UAAU,EACvB,GAAG,EAAE,OAAO,CAAC,WAAW,EACxB,SAAS,EAAE,QAAQ,EAAE,EACrB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,QAAQ,EAEhB,WAAW,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC;;;;GAuCpD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,aAAa,QAmB1G"}
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/jamnp-s/network.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAQ,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAAE,KAAK,QAAQ,EAAe,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAKxE,wBAAsB,KAAK,CACzB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACpC,WAAW,EAAE,UAAU,EACvB,GAAG,EAAE,OAAO,CAAC,WAAW,EACxB,SAAS,EAAE,QAAQ,EAAE,EACrB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,QAAQ,EAEhB,WAAW,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC;;;;;GA0CpD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,aAAa,QAmB1G"}
@@ -6,6 +6,7 @@ import { OK } from "#@typeberry/utils";
6
6
  import { Connections } from "./peers.js";
7
7
  import { StreamManager } from "./stream-manager.js";
8
8
  import { SyncTask } from "./tasks/sync.js";
9
+ import { TicketDistributionTask } from "./tasks/ticket-distribution.js";
9
10
  import { handleAsyncErrors } from "./utils.js";
10
11
  const logger = Logger.new(import.meta.filename, "jamnps");
11
12
  export async function setup(bind, genesisHash, key, bootnodes, spec, blocks,
@@ -24,10 +25,12 @@ onNewBlocks) {
24
25
  const streamManager = new StreamManager();
25
26
  // start the networking tasks
26
27
  const syncTask = SyncTask.start(spec, blake2b, streamManager, connections, blocks, onNewBlocks);
28
+ const ticketTask = TicketDistributionTask.start(streamManager, connections);
27
29
  setImmediate(async () => {
28
30
  while (network.isRunning) {
29
31
  await setTimeout(3000);
30
32
  syncTask.maintainSync();
33
+ ticketTask.maintainDistribution();
31
34
  }
32
35
  });
33
36
  // TODO [ToDr] This design is a bit weird,
@@ -40,6 +43,7 @@ onNewBlocks) {
40
43
  return {
41
44
  network,
42
45
  syncTask,
46
+ ticketTask,
43
47
  streamManager,
44
48
  };
45
49
  }
@@ -14,6 +14,10 @@ import { type StreamHandler, type StreamId, type StreamMessageSender } from "./s
14
14
  export declare const STREAM_KIND_GENERATOR_TO_PROXY: 131 & import("@typeberry/numbers").WithBytesRepresentation<1>;
15
15
  export declare const STREAM_KIND_PROXY_TO_ALL: 132 & import("@typeberry/numbers").WithBytesRepresentation<1>;
16
16
  type STREAM_KIND = typeof STREAM_KIND_GENERATOR_TO_PROXY | typeof STREAM_KIND_PROXY_TO_ALL;
17
+ /**
18
+ * Network protocol message for distributing a single ticket.
19
+ * Used in CE-131/CE-132 streams.
20
+ */
17
21
  export declare class TicketDistributionRequest extends WithDebug {
18
22
  readonly epochIndex: Epoch;
19
23
  readonly ticket: SignedTicket;
@@ -1 +1 @@
1
- {"version":3,"file":"ce-131-ce-132-safrole-ticket-distribution.d.ts","sourceRoot":"","sources":["../../../../../../packages/jam/jamnp-s/protocol/ce-131-ce-132-safrole-ticket-distribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,WAAW,EAA2B,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AAE3G;;;;;;GAMG;AAEH,eAAO,MAAM,8BAA8B,+DAAuB,CAAC;AACnE,eAAO,MAAM,wBAAwB,+DAAuB,CAAC;AAE7D,KAAK,WAAW,GAAG,OAAO,8BAA8B,GAAG,OAAO,wBAAwB,CAAC;AAE3F,qBAAa,yBAA0B,SAAQ,SAAS;aAWpC,UAAU,EAAE,KAAK;aACjB,MAAM,EAAE,YAAY;IAXtC,MAAM,CAAC,KAAK;;;;;;QAGT;IAEH,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,yBAAyB,CAAC;IAI5E,OAAO;CAMR;AAID,qBAAa,aAAa,CAAC,CAAC,SAAS,WAAW,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;aAEzD,IAAI,EAAE,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADjB,IAAI,EAAE,CAAC,EACN,gBAAgB,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI;IAGtF,eAAe,CAAC,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,SAAS,GAAG,IAAI;IAOtE,OAAO,CAAC,SAAS,EAAE,QAAQ;CAC5B;AAED,qBAAa,aAAa,CAAC,CAAC,SAAS,WAAW,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;aAC/C,IAAI,EAAE,CAAC;gBAAP,IAAI,EAAE,CAAC;IAEnC,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAKlD,OAAO,CAAC,SAAS,EAAE,QAAQ;IAE3B,UAAU,CAAC,MAAM,EAAE,mBAAmB,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY;CAKhF"}
1
+ {"version":3,"file":"ce-131-ce-132-safrole-ticket-distribution.d.ts","sourceRoot":"","sources":["../../../../../../packages/jam/jamnp-s/protocol/ce-131-ce-132-safrole-ticket-distribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,WAAW,EAA2B,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AAE3G;;;;;;GAMG;AAEH,eAAO,MAAM,8BAA8B,+DAAuB,CAAC;AACnE,eAAO,MAAM,wBAAwB,+DAAuB,CAAC;AAE7D,KAAK,WAAW,GAAG,OAAO,8BAA8B,GAAG,OAAO,wBAAwB,CAAC;AAE3F;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;aAWpC,UAAU,EAAE,KAAK;aACjB,MAAM,EAAE,YAAY;IAXtC,MAAM,CAAC,KAAK;;;;;;QAGT;IAEH,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,yBAAyB,CAAC;IAI5E,OAAO;CAMR;AAID,qBAAa,aAAa,CAAC,CAAC,SAAS,WAAW,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;aAEzD,IAAI,EAAE,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADjB,IAAI,EAAE,CAAC,EACN,gBAAgB,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI;IAGtF,eAAe,CAAC,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,SAAS,GAAG,IAAI;IAOtE,OAAO,CAAC,SAAS,EAAE,QAAQ;CAC5B;AAED,qBAAa,aAAa,CAAC,CAAC,SAAS,WAAW,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;aAC/C,IAAI,EAAE,CAAC;gBAAP,IAAI,EAAE,CAAC;IAEnC,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAKlD,OAAO,CAAC,SAAS,EAAE,QAAQ;IAE3B,UAAU,CAAC,MAAM,EAAE,mBAAmB,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY;CAKhF"}
@@ -12,6 +12,10 @@ import { tryAsStreamKind } from "./stream.js";
12
12
  */
13
13
  export const STREAM_KIND_GENERATOR_TO_PROXY = tryAsStreamKind(131);
14
14
  export const STREAM_KIND_PROXY_TO_ALL = tryAsStreamKind(132);
15
+ /**
16
+ * Network protocol message for distributing a single ticket.
17
+ * Used in CE-131/CE-132 streams.
18
+ */
15
19
  export class TicketDistributionRequest extends WithDebug {
16
20
  epochIndex;
17
21
  ticket;
@@ -0,0 +1,33 @@
1
+ import type { Epoch } from "#@typeberry/block";
2
+ import type { SignedTicket } from "#@typeberry/block/tickets.js";
3
+ import type { Connections } from "../peers.js";
4
+ import type { StreamManager } from "../stream-manager.js";
5
+ /**
6
+ * Manages distribution of Safrole tickets to connected peers.
7
+ *
8
+ * Uses CE-132 (proxy-to-all) for direct broadcast to all peers.
9
+ * Implements a maintain pattern similar to SyncTask: tickets are collected
10
+ * and periodically distributed to peers that haven't received them yet.
11
+ */
12
+ export declare class TicketDistributionTask {
13
+ private readonly streamManager;
14
+ private readonly connections;
15
+ static start(streamManager: StreamManager, connections: Connections): TicketDistributionTask;
16
+ /** Pending tickets waiting to be distributed to peers */
17
+ private pendingTickets;
18
+ /** Current epoch being tracked (cleared when epoch changes) */
19
+ private currentEpoch;
20
+ private constructor();
21
+ /**
22
+ * Should be called periodically to distribute pending tickets to connected peers.
23
+ */
24
+ maintainDistribution(): void;
25
+ /**
26
+ * Add a ticket to the pending queue for distribution.
27
+ * Clears pending tickets when epoch changes.
28
+ * Deduplicates tickets based on signature.
29
+ */
30
+ addTicket(epochIndex: Epoch, ticket: SignedTicket): void;
31
+ private onTicketReceived;
32
+ }
33
+ //# sourceMappingURL=ticket-distribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket-distribution.d.ts","sourceRoot":"","sources":["../../../../../../packages/jam/jamnp-s/tasks/ticket-distribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGhE,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAe1D;;;;;;GAMG;AACH,qBAAa,sBAAsB;IAuB/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAvB9B,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW;IAgBnE,yDAAyD;IACzD,OAAO,CAAC,cAAc,CAA0D;IAChF,+DAA+D;IAC/D,OAAO,CAAC,YAAY,CAAsB;IAE1C,OAAO;IAKP;;OAEG;IACH,oBAAoB;IAkDpB;;;;OAIG;IACH,SAAS,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY;IAgCjD,OAAO,CAAC,gBAAgB;CAKzB"}
@@ -0,0 +1,115 @@
1
+ import { Logger } from "#@typeberry/logger";
2
+ import { OK } from "#@typeberry/utils";
3
+ import { ce131 } from "../protocol/index.js";
4
+ const logger = Logger.new(import.meta.filename, "net:tickets");
5
+ /** Aux data to track which tickets have been sent to each peer (using indices) */
6
+ const TICKET_AUX = {
7
+ id: Symbol("tickets"),
8
+ };
9
+ /**
10
+ * Manages distribution of Safrole tickets to connected peers.
11
+ *
12
+ * Uses CE-132 (proxy-to-all) for direct broadcast to all peers.
13
+ * Implements a maintain pattern similar to SyncTask: tickets are collected
14
+ * and periodically distributed to peers that haven't received them yet.
15
+ */
16
+ export class TicketDistributionTask {
17
+ streamManager;
18
+ connections;
19
+ static start(streamManager, connections) {
20
+ const task = new TicketDistributionTask(streamManager, connections);
21
+ // server mode: receive tickets from peers
22
+ streamManager.registerIncomingHandlers(new ce131.ServerHandler(ce131.STREAM_KIND_PROXY_TO_ALL, (epochIndex, ticket) => {
23
+ task.onTicketReceived(epochIndex, ticket);
24
+ }));
25
+ // client mode: send tickets to peers
26
+ streamManager.registerOutgoingHandlers(new ce131.ClientHandler(ce131.STREAM_KIND_PROXY_TO_ALL));
27
+ return task;
28
+ }
29
+ /** Pending tickets waiting to be distributed to peers */
30
+ pendingTickets = [];
31
+ /** Current epoch being tracked (cleared when epoch changes) */
32
+ currentEpoch = null;
33
+ constructor(streamManager, connections) {
34
+ this.streamManager = streamManager;
35
+ this.connections = connections;
36
+ }
37
+ /**
38
+ * Should be called periodically to distribute pending tickets to connected peers.
39
+ */
40
+ maintainDistribution() {
41
+ if (this.currentEpoch === null) {
42
+ return; // No tickets to distribute yet
43
+ }
44
+ /** `this` is mutable and TS can't narrow this.currentEpoch inside the callback closure */
45
+ const currentEpoch = this.currentEpoch;
46
+ // Iterate through all pending tickets
47
+ for (let ticketIdx = 0; ticketIdx < this.pendingTickets.length; ticketIdx++) {
48
+ const { epochIndex, ticket } = this.pendingTickets[ticketIdx];
49
+ // Try to send to each connected peer
50
+ for (const peerInfo of this.connections.getConnectedPeers()) {
51
+ this.connections.withAuxData(peerInfo.peerId, TICKET_AUX, (maybeAux) => {
52
+ const shouldReset = maybeAux === undefined || maybeAux.epoch !== currentEpoch;
53
+ const aux = shouldReset ? { epoch: currentEpoch, seen: new Set() } : maybeAux;
54
+ if (peerInfo.peerRef === null) {
55
+ return aux;
56
+ }
57
+ // Check if we already sent this ticket to this peer
58
+ if (aux.seen.has(ticketIdx)) {
59
+ return aux; // Already sent
60
+ }
61
+ // Send the ticket - only mark as sent after successful send
62
+ try {
63
+ this.streamManager.withNewStream(peerInfo.peerRef, ce131.STREAM_KIND_PROXY_TO_ALL, (handler, sender) => {
64
+ logger.trace `[${peerInfo.peerId}] <-- Sending ticket for epoch ${epochIndex}`;
65
+ handler.sendTicket(sender, epochIndex, ticket);
66
+ return OK;
67
+ });
68
+ // Mark as sent only after successful send, so failed sends will be retried
69
+ aux.seen.add(ticketIdx);
70
+ }
71
+ catch (e) {
72
+ logger.warn `[${peerInfo.peerId}] Failed to send ticket for epoch ${epochIndex}: ${e}`;
73
+ }
74
+ return aux;
75
+ });
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Add a ticket to the pending queue for distribution.
81
+ * Clears pending tickets when epoch changes.
82
+ * Deduplicates tickets based on signature.
83
+ */
84
+ addTicket(epochIndex, ticket) {
85
+ // Check if epoch changed - if so, clear old tickets
86
+ if (this.currentEpoch !== null && this.currentEpoch !== epochIndex) {
87
+ logger.log `[addTicket] Epoch changed from ${this.currentEpoch} to ${epochIndex}, clearing ${this.pendingTickets.length} old tickets`;
88
+ this.pendingTickets = [];
89
+ // Note: We don't need to clear aux data for all peers here.
90
+ // The aux data contains the epoch, so maintainDistribution will lazily
91
+ // reset it when it detects an epoch mismatch. This handles both connected
92
+ // and disconnected peers correctly.
93
+ }
94
+ this.currentEpoch = epochIndex;
95
+ /**
96
+ * Deduplicate: check if a ticket with the same signature already exists
97
+ *
98
+ * Here we are risking "poisoning" the local pendingTickets - i.e:
99
+ * 1. The adversary sees a signature and swaps the ticket attempt to something different.
100
+ * 2. This creates an invalid ticket, but prevents a valid ticket with the same signature from being included and distributed.
101
+ *
102
+ * TODO [MaSi]: The poisoning risk should be fixed during implementation of ticket validation.
103
+ */
104
+ const isDuplicate = this.pendingTickets.some((pending) => pending.epochIndex === epochIndex && pending.ticket.signature.isEqualTo(ticket.signature));
105
+ if (!isDuplicate) {
106
+ this.pendingTickets.push({ epochIndex, ticket });
107
+ logger.info `[addTicket] Added ticket for epoch ${epochIndex}, total: ${this.pendingTickets.length}`;
108
+ }
109
+ }
110
+ onTicketReceived(epochIndex, ticket) {
111
+ logger.trace `Received ticket for epoch ${epochIndex}, attempt ${ticket.attempt}`;
112
+ // Add to pending queue for potential re-distribution
113
+ this.addTicket(epochIndex, ticket);
114
+ }
115
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ticket-distribution.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket-distribution.test.d.ts","sourceRoot":"","sources":["../../../../../../packages/jam/jamnp-s/tasks/ticket-distribution.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,219 @@
1
+ import assert from "node:assert";
2
+ import { describe, it } from "node:test";
3
+ import { setTimeout } from "node:timers/promises";
4
+ import { tryAsEpoch } from "#@typeberry/block";
5
+ import { SignedTicket, tryAsTicketAttempt } from "#@typeberry/block/tickets.js";
6
+ import { Bytes } from "#@typeberry/bytes";
7
+ import { BANDERSNATCH_PROOF_BYTES } from "#@typeberry/crypto";
8
+ import { Logger } from "#@typeberry/logger";
9
+ import { createTestPeerPair, MockNetwork } from "#@typeberry/networking/testing.js";
10
+ import { OK } from "#@typeberry/utils";
11
+ import { Connections } from "../peers.js";
12
+ import { StreamManager } from "../stream-manager.js";
13
+ import { TicketDistributionTask } from "./ticket-distribution.js";
14
+ const logger = Logger.new(import.meta.filename, "test:tickets");
15
+ const TEST_EPOCH = tryAsEpoch(42);
16
+ const OTHER_EPOCH = tryAsEpoch(43);
17
+ function createTestTicket(attempt, signatureByte = 0) {
18
+ const signatureBytes = Bytes.zero(BANDERSNATCH_PROOF_BYTES);
19
+ // Make signature unique based on attempt and signatureByte
20
+ signatureBytes.raw[0] = attempt;
21
+ signatureBytes.raw[1] = signatureByte;
22
+ return SignedTicket.create({
23
+ attempt: tryAsTicketAttempt(attempt),
24
+ signature: signatureBytes.asOpaque(),
25
+ });
26
+ }
27
+ describe("TicketDistributionTask", () => {
28
+ async function init(name) {
29
+ const network = new MockNetwork(name);
30
+ const streamManager = new StreamManager();
31
+ const connections = new Connections(network);
32
+ // Track received tickets for verification
33
+ const receivedTickets = [];
34
+ // Use real TicketDistributionTask
35
+ const ticketTask = TicketDistributionTask.start(streamManager, connections);
36
+ // Intercept received tickets by wrapping onTicketReceived behavior
37
+ // The task already adds received tickets to pending queue via addTicket,
38
+ // so we can track them by checking the pending queue growth or by
39
+ // hooking into the CE-131 server handler directly
40
+ const originalAddTicket = ticketTask.addTicket.bind(ticketTask);
41
+ ticketTask.addTicket = (epochIndex, ticket) => {
42
+ receivedTickets.push({ epochIndex, ticket });
43
+ originalAddTicket(epochIndex, ticket);
44
+ };
45
+ // Setup peer listeners for incoming streams
46
+ network.peers.onPeerConnected((peer) => {
47
+ peer.addOnIncomingStream((stream) => {
48
+ streamManager.onIncomingStream(peer, stream);
49
+ return OK;
50
+ });
51
+ return OK;
52
+ });
53
+ let connectionIdx = 0;
54
+ const openConnection = (other) => {
55
+ const [self, peer] = createTestPeerPair(connectionIdx++, name, other.name);
56
+ network._peers.peerConnected(peer);
57
+ other.network._peers.peerConnected(self);
58
+ return [self, peer];
59
+ };
60
+ return {
61
+ name,
62
+ ticketTask,
63
+ network,
64
+ connections,
65
+ streamManager,
66
+ openConnection,
67
+ receivedTickets,
68
+ };
69
+ }
70
+ async function tick() {
71
+ logger.log `tick`;
72
+ await setTimeout(10);
73
+ }
74
+ it("should distribute ticket to all connected peers via maintainDistribution", async () => {
75
+ const self = await init("self");
76
+ const peer1 = await init("peer1");
77
+ const peer2 = await init("peer2");
78
+ self.openConnection(peer1);
79
+ self.openConnection(peer2);
80
+ await tick();
81
+ const ticket = createTestTicket(0);
82
+ self.ticketTask.addTicket(TEST_EPOCH, ticket);
83
+ self.ticketTask.maintainDistribution();
84
+ await tick();
85
+ // Both peers should have received the ticket
86
+ assert.strictEqual(peer1.receivedTickets.length, 1);
87
+ assert.strictEqual(peer1.receivedTickets[0].epochIndex, TEST_EPOCH);
88
+ assert.deepStrictEqual(peer1.receivedTickets[0].ticket, ticket);
89
+ assert.strictEqual(peer2.receivedTickets.length, 1);
90
+ assert.strictEqual(peer2.receivedTickets[0].epochIndex, TEST_EPOCH);
91
+ assert.deepStrictEqual(peer2.receivedTickets[0].ticket, ticket);
92
+ });
93
+ it("should receive tickets from peers", async () => {
94
+ const self = await init("self");
95
+ const peer1 = await init("peer1");
96
+ self.openConnection(peer1);
97
+ await tick();
98
+ const ticket = createTestTicket(1);
99
+ peer1.ticketTask.addTicket(TEST_EPOCH, ticket);
100
+ peer1.ticketTask.maintainDistribution();
101
+ await tick();
102
+ assert.strictEqual(self.receivedTickets.length, 1);
103
+ assert.strictEqual(self.receivedTickets[0].epochIndex, TEST_EPOCH);
104
+ assert.deepStrictEqual(self.receivedTickets[0].ticket, ticket);
105
+ });
106
+ it("should handle multiple tickets", async () => {
107
+ const self = await init("self");
108
+ const peer1 = await init("peer1");
109
+ self.openConnection(peer1);
110
+ await tick();
111
+ const ticket0 = createTestTicket(0);
112
+ const ticket1 = createTestTicket(1);
113
+ self.ticketTask.addTicket(TEST_EPOCH, ticket0);
114
+ self.ticketTask.addTicket(TEST_EPOCH, ticket1);
115
+ self.ticketTask.maintainDistribution();
116
+ await tick();
117
+ assert.strictEqual(peer1.receivedTickets.length, 2);
118
+ assert.deepStrictEqual(peer1.receivedTickets[0].ticket, ticket0);
119
+ assert.deepStrictEqual(peer1.receivedTickets[1].ticket, ticket1);
120
+ });
121
+ it("should handle no connected peers gracefully", async () => {
122
+ const self = await init("self");
123
+ const ticket = createTestTicket(0);
124
+ // Should not throw
125
+ self.ticketTask.addTicket(TEST_EPOCH, ticket);
126
+ self.ticketTask.maintainDistribution();
127
+ await tick();
128
+ });
129
+ it("should deduplicate tickets with same signature", async () => {
130
+ const self = await init("self");
131
+ const peer1 = await init("peer1");
132
+ self.openConnection(peer1);
133
+ await tick();
134
+ const ticket = createTestTicket(0);
135
+ // Add same ticket twice
136
+ self.ticketTask.addTicket(TEST_EPOCH, ticket);
137
+ self.ticketTask.addTicket(TEST_EPOCH, ticket);
138
+ self.ticketTask.maintainDistribution();
139
+ await tick();
140
+ // Peer should only receive it once
141
+ assert.strictEqual(peer1.receivedTickets.length, 1);
142
+ });
143
+ it("should not send same ticket to same peer twice", async () => {
144
+ const self = await init("self");
145
+ const peer1 = await init("peer1");
146
+ self.openConnection(peer1);
147
+ await tick();
148
+ const ticket = createTestTicket(0);
149
+ self.ticketTask.addTicket(TEST_EPOCH, ticket);
150
+ // Call maintainDistribution twice
151
+ self.ticketTask.maintainDistribution();
152
+ await tick();
153
+ self.ticketTask.maintainDistribution();
154
+ await tick();
155
+ // Peer should only receive the ticket once (aux data tracks sent indices)
156
+ assert.strictEqual(peer1.receivedTickets.length, 1);
157
+ });
158
+ it("should clear tickets when epoch changes", async () => {
159
+ const self = await init("self");
160
+ const peer1 = await init("peer1");
161
+ self.openConnection(peer1);
162
+ await tick();
163
+ const ticket1 = createTestTicket(0);
164
+ const ticket2 = createTestTicket(1);
165
+ // Add ticket for first epoch
166
+ self.ticketTask.addTicket(TEST_EPOCH, ticket1);
167
+ // Change epoch - this should clear old tickets
168
+ self.ticketTask.addTicket(OTHER_EPOCH, ticket2);
169
+ self.ticketTask.maintainDistribution();
170
+ await tick();
171
+ // Peer should only receive the second ticket (first was cleared on epoch change)
172
+ assert.strictEqual(peer1.receivedTickets.length, 1);
173
+ assert.strictEqual(peer1.receivedTickets[0].epochIndex, OTHER_EPOCH);
174
+ assert.deepStrictEqual(peer1.receivedTickets[0].ticket, ticket2);
175
+ });
176
+ it("should send new tickets to newly connected peers", async () => {
177
+ const self = await init("self");
178
+ const peer1 = await init("peer1");
179
+ const peer2 = await init("peer2");
180
+ // Connect peer1 first
181
+ self.openConnection(peer1);
182
+ await tick();
183
+ const ticket = createTestTicket(0);
184
+ self.ticketTask.addTicket(TEST_EPOCH, ticket);
185
+ self.ticketTask.maintainDistribution();
186
+ await tick();
187
+ // Now connect peer2 after ticket was already distributed to peer1
188
+ self.openConnection(peer2);
189
+ await tick();
190
+ // Run maintainDistribution again - peer2 should get the ticket
191
+ self.ticketTask.maintainDistribution();
192
+ await tick();
193
+ assert.strictEqual(peer1.receivedTickets.length, 1);
194
+ assert.strictEqual(peer2.receivedTickets.length, 1);
195
+ assert.deepStrictEqual(peer2.receivedTickets[0].ticket, ticket);
196
+ });
197
+ it("should re-distribute received tickets to other peers", async () => {
198
+ const self = await init("self");
199
+ const peer1 = await init("peer1");
200
+ const peer2 = await init("peer2");
201
+ // Self connects to both peers
202
+ self.openConnection(peer1);
203
+ self.openConnection(peer2);
204
+ await tick();
205
+ // peer1 sends a ticket to self
206
+ const ticket = createTestTicket(0);
207
+ peer1.ticketTask.addTicket(TEST_EPOCH, ticket);
208
+ peer1.ticketTask.maintainDistribution();
209
+ await tick();
210
+ // Self receives the ticket (via onTicketReceived -> addTicket)
211
+ assert.strictEqual(self.receivedTickets.length, 1);
212
+ // Self should re-distribute to peer2 (and peer1, but peer1 already has it)
213
+ self.ticketTask.maintainDistribution();
214
+ await tick();
215
+ // peer2 should now have received the ticket from self
216
+ assert.strictEqual(peer2.receivedTickets.length, 1);
217
+ assert.deepStrictEqual(peer2.receivedTickets[0].ticket, ticket);
218
+ });
219
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAc,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,mBAAmB,CAAC;AAe/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAkC,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAKnF,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAWhE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAsB,IAAI,CACxB,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,SAAS,EAAE,SAAS,GAAG,IAAI,GAC1B,OAAO,CAAC,OAAO,CAAC,CAuJlB"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/main.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAc,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEzF,OAAO,EAAE,KAAK,SAAS,EAAc,MAAM,mBAAmB,CAAC;AAe/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAkC,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAKnF,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAWhE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,oBAAoB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAsB,IAAI,CACxB,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,EAClC,SAAS,EAAE,SAAS,GAAG,IAAI,GAC1B,OAAO,CAAC,OAAO,CAAC,CAuKlB"}
@@ -1,4 +1,5 @@
1
1
  import { isMainThread } from "node:worker_threads";
2
+ import { AUTHORSHIP_NETWORK_PORT } from "#@typeberry/comms-authorship-network";
2
3
  import { PvmBackend } from "#@typeberry/config";
3
4
  import { initWasm } from "#@typeberry/crypto";
4
5
  import { deriveBandersnatchSecretKey, deriveEd25519SecretKey, trivialSeed, } from "#@typeberry/crypto/key-derivation.js";
@@ -8,8 +9,8 @@ import { NetworkingConfig } from "#@typeberry/jam-network";
8
9
  import { Listener } from "#@typeberry/listener";
9
10
  import { tryAsU16, tryAsU32 } from "#@typeberry/numbers";
10
11
  import { CURRENT_SUITE, CURRENT_VERSION, Result, version } from "#@typeberry/utils";
11
- import { DirectWorkerConfig } from "#@typeberry/workers-api";
12
- import { InMemWorkerConfig, LmdbWorkerConfig } from "#@typeberry/workers-api-node";
12
+ import { DirectPort, DirectWorkerConfig } from "#@typeberry/workers-api";
13
+ import { InMemWorkerConfig, LmdbWorkerConfig, ThreadPort } from "#@typeberry/workers-api-node";
13
14
  import { getChainSpec, getDatabasePath, initializeDatabase, logger } from "./common.js";
14
15
  import { initializeExtensions } from "./extensions.js";
15
16
  import * as metrics from "./metrics.js";
@@ -36,8 +37,8 @@ export async function main(config, withRelPath, telemetry) {
36
37
  }),
37
38
  };
38
39
  const importerConfig = isInMemory
39
- ? { type: "inmem", config: InMemWorkerConfig.new(importerParams) }
40
- : { type: "lmdb", config: LmdbWorkerConfig.new(importerParams) };
40
+ ? { isInMemory, config: InMemWorkerConfig.new(importerParams) }
41
+ : { isInMemory, config: LmdbWorkerConfig.new(importerParams) };
41
42
  // Initialize the database with genesis state and block if there isn't one.
42
43
  logger.info `🛢️ Opening database at ${dbPath}`;
43
44
  const rootDb = importerConfig.config.openDatabase({ readonly: false });
@@ -50,7 +51,7 @@ export async function main(config, withRelPath, telemetry) {
50
51
  // Start block importer
51
52
  let importer;
52
53
  let closeImporter;
53
- if (importerConfig.type === "inmem") {
54
+ if (importerConfig.isInMemory) {
54
55
  ({ importer, finish: closeImporter } = await startImporterDirect(DirectWorkerConfig.new({
55
56
  ...importerConfig.config,
56
57
  blocksDb: rootDb.getBlocksDb(),
@@ -91,9 +92,24 @@ export async function main(config, withRelPath, telemetry) {
91
92
  },
92
93
  ],
93
94
  };
94
- const closeAuthorship = await initAuthorship(importer, config.isAuthoring, config.isFastForward, rootDb, baseConfig, authorshipKeys, isInMemory);
95
- // Networking initialization
96
- const closeNetwork = await initNetwork(importer, rootDb, baseConfig, genesisHeaderHash, config.network, bestHeader, isInMemory);
95
+ const { networkingParams, authorshipParams } = isInMemory
96
+ ? (() => {
97
+ const [tx, rx] = DirectPort.pair();
98
+ return {
99
+ networkingParams: { isInMemory, rootDb, authorshipPort: tx },
100
+ authorshipParams: { isInMemory, rootDb, networkingPort: rx },
101
+ };
102
+ })()
103
+ : (() => {
104
+ const [tx, rx] = ThreadPort.pair(chainSpec);
105
+ return {
106
+ networkingParams: { isInMemory, rootDb, authorshipPort: tx },
107
+ authorshipParams: { isInMemory, rootDb, networkingPort: rx },
108
+ };
109
+ })();
110
+ // Networking initialization (before authorship so we can relay tickets)
111
+ const { closeNetwork } = await initNetwork(importer, networkingParams, baseConfig, genesisHeaderHash, config.network, bestHeader);
112
+ const { closeAuthorship } = await initAuthorship(importer, config.isAuthoring, config.isFastForward, authorshipParams, baseConfig, authorshipKeys);
97
113
  const api = {
98
114
  chainSpec,
99
115
  async importBlock(block) {
@@ -110,14 +126,14 @@ export async function main(config, withRelPath, telemetry) {
110
126
  return importer.sendGetBestStateRootHash();
111
127
  },
112
128
  async close() {
113
- logger.log `[main] ☠️ Closing the importer`;
114
- await closeImporter();
115
- logger.log `[main] ☠️ Closing the extensions`;
116
- closeExtensions();
117
129
  logger.log `[main] ☠️ Closing the authorship module`;
118
130
  await closeAuthorship();
119
131
  logger.log `[main] ☠️ Closing the networking module`;
120
132
  await closeNetwork();
133
+ logger.log `[main] ☠️ Closing the importer`;
134
+ await closeImporter();
135
+ logger.log `[main] ☠️ Closing the extensions`;
136
+ closeExtensions();
121
137
  logger.log `[main] 🛢️ Closing the database`;
122
138
  await rootDb.close();
123
139
  logger.log `[main] 📳 Closing telemetry`;
@@ -127,35 +143,48 @@ export async function main(config, withRelPath, telemetry) {
127
143
  };
128
144
  return api;
129
145
  }
130
- const initAuthorship = async (importer, isAuthoring, isFastForward, rootDb, baseConfig, authorshipKeys, isInMemory) => {
146
+ const initAuthorship = async (importer, isAuthoring, isFastForward, params, baseConfig, authorshipKeys) => {
131
147
  if (!isAuthoring) {
132
148
  logger.log `✍️ Authorship off: disabled`;
133
- return () => Promise.resolve();
149
+ return {
150
+ closeAuthorship: () => {
151
+ params.networkingPort.close();
152
+ return Promise.resolve();
153
+ },
154
+ authorshipWorker: null,
155
+ };
134
156
  }
135
157
  logger.info `✍️ Starting block generator.`;
136
158
  const workerParams = { ...authorshipKeys, isFastForward };
137
- const { generator, finish } = isInMemory
159
+ const { generator, worker, finish } = params.isInMemory
138
160
  ? await startBlockGenerator(DirectWorkerConfig.new({
139
161
  ...baseConfig,
140
- blocksDb: rootDb.getBlocksDb(),
141
- statesDb: rootDb.getStatesDb(),
162
+ blocksDb: params.rootDb.getBlocksDb(),
163
+ statesDb: params.rootDb.getStatesDb(),
142
164
  workerParams,
143
- }))
165
+ }), params.networkingPort)
144
166
  : await spawnBlockGeneratorWorker(LmdbWorkerConfig.new({
145
167
  ...baseConfig,
146
168
  workerParams,
169
+ ports: new Map([[AUTHORSHIP_NETWORK_PORT, params.networkingPort]]),
147
170
  }));
148
171
  // relay blocks from generator to importer
149
172
  generator.setOnBlock(async (block) => {
150
173
  logger.log `✍️ Produced block at ${block.header.view().timeSlotIndex.materialize()}`;
151
174
  await importer.sendImportBlock(block);
152
175
  });
153
- return finish;
176
+ return { closeAuthorship: finish, authorshipWorker: worker };
154
177
  };
155
- const initNetwork = async (importer, rootDb, baseConfig, genesisHeaderHash, networkConfig, bestHeader, isInMemory) => {
178
+ const initNetwork = async (importer, params, baseConfig, genesisHeaderHash, networkConfig, bestHeader) => {
156
179
  if (networkConfig === null) {
157
180
  logger.log `🛜 Networking off: no config`;
158
- return () => Promise.resolve();
181
+ return {
182
+ closeNetwork: async () => {
183
+ params.authorshipPort.close();
184
+ },
185
+ networkApi: null,
186
+ networkWorker: null,
187
+ };
159
188
  }
160
189
  const { key, host, port, bootnodes } = networkConfig;
161
190
  const networkingConfig = NetworkingConfig.create({
@@ -165,16 +194,17 @@ const initNetwork = async (importer, rootDb, baseConfig, genesisHeaderHash, netw
165
194
  port: tryAsU16(port),
166
195
  bootnodes: bootnodes.map((node) => node.toString()),
167
196
  });
168
- const { network, finish } = isInMemory
197
+ const { network, worker, finish } = params.isInMemory
169
198
  ? await startNetwork(DirectWorkerConfig.new({
170
199
  ...baseConfig,
171
- blocksDb: rootDb.getBlocksDb(),
172
- statesDb: rootDb.getStatesDb(),
200
+ blocksDb: params.rootDb.getBlocksDb(),
201
+ statesDb: params.rootDb.getStatesDb(),
173
202
  workerParams: networkingConfig,
174
- }))
203
+ }), params.authorshipPort)
175
204
  : await spawnNetworkWorker(LmdbWorkerConfig.new({
176
205
  ...baseConfig,
177
206
  workerParams: networkingConfig,
207
+ ports: new Map([[AUTHORSHIP_NETWORK_PORT, params.authorshipPort]]),
178
208
  }));
179
209
  // relay blocks from networking to importer
180
210
  network.setOnBlocks(async (newBlocks) => {
@@ -186,5 +216,5 @@ const initNetwork = async (importer, rootDb, baseConfig, genesisHeaderHash, netw
186
216
  bestHeader.on((header) => {
187
217
  network.sendNewHeader(header);
188
218
  });
189
- return finish;
219
+ return { closeNetwork: finish, networkApi: network, networkWorker: worker };
190
220
  };
@@ -1,9 +1,10 @@
1
+ import type { Worker } from "node:worker_threads";
1
2
  import * as blockAuthorship from "#@typeberry/block-authorship";
2
3
  import type { BlocksDb, LeafDb, StatesDb } from "#@typeberry/database";
3
4
  import * as importer from "#@typeberry/importer";
4
5
  import * as jamNetwork from "#@typeberry/jam-network";
5
6
  import type { SerializedState } from "#@typeberry/state-merkleization";
6
- import { type DirectWorkerConfig } from "#@typeberry/workers-api";
7
+ import { type DirectPort, type DirectWorkerConfig } from "#@typeberry/workers-api";
7
8
  import { type LmdbWorkerConfig } from "#@typeberry/workers-api-node";
8
9
  export declare function spawnImporterWorker(config: LmdbWorkerConfig<importer.ImporterConfig>): Promise<{
9
10
  importer: import("@typeberry/workers-api").Tx<{
@@ -858,9 +859,12 @@ export declare function spawnNetworkWorker(config: LmdbWorkerConfig<jamNetwork.N
858
859
  response: import("@typeberry/codec").Descriptor<void, void>;
859
860
  };
860
861
  }>;
862
+ worker: Worker;
861
863
  finish: () => Promise<void>;
862
864
  }>;
863
- export declare function startNetwork(config: DirectWorkerConfig<jamNetwork.NetworkingConfig>): ReturnType<typeof spawnNetworkWorker>;
865
+ export declare function startNetwork(config: DirectWorkerConfig<jamNetwork.NetworkingConfig>, authorshipPort: DirectPort): Promise<Omit<Awaited<ReturnType<typeof spawnNetworkWorker>>, "worker"> & {
866
+ worker: Worker | null;
867
+ }>;
864
868
  export declare function spawnBlockGeneratorWorker(config: LmdbWorkerConfig<blockAuthorship.BlockAuthorshipConfig>): Promise<{
865
869
  generator: import("@typeberry/workers-api").Tx<{
866
870
  finish: {
@@ -1121,7 +1125,10 @@ export declare function spawnBlockGeneratorWorker(config: LmdbWorkerConfig<block
1121
1125
  response: import("@typeberry/codec").Descriptor<void, void>;
1122
1126
  };
1123
1127
  }>;
1128
+ worker: Worker;
1124
1129
  finish: () => Promise<void>;
1125
1130
  }>;
1126
- export declare function startBlockGenerator(config: DirectWorkerConfig<blockAuthorship.BlockAuthorshipConfig>): ReturnType<typeof spawnBlockGeneratorWorker>;
1131
+ export declare function startBlockGenerator(config: DirectWorkerConfig<blockAuthorship.BlockAuthorshipConfig>, networkingPort: DirectPort): Promise<Omit<Awaited<ReturnType<typeof spawnBlockGeneratorWorker>>, "worker"> & {
1132
+ worker: Worker | null;
1133
+ }>;
1127
1134
  //# sourceMappingURL=workers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/workers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../../../../packages/jam/node/workers.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import * as blockAuthorship from "#@typeberry/block-authorship";
2
+ import { protocol } from "#@typeberry/comms-authorship-network";
2
3
  import * as importer from "#@typeberry/importer";
3
4
  import * as jamNetwork from "#@typeberry/jam-network";
4
- import { startSameThread } from "#@typeberry/workers-api";
5
+ import { Channel, startSameThread } from "#@typeberry/workers-api";
5
6
  import { spawnWorker } from "#@typeberry/workers-api-node";
6
7
  export async function spawnImporterWorker(config) {
7
8
  const { api, workerFinished } = spawnWorker(importer.protocol, importer.WORKER, config, importer.ImporterConfig.Codec);
@@ -27,9 +28,10 @@ export async function startImporterDirect(config) {
27
28
  };
28
29
  }
29
30
  export async function spawnNetworkWorker(config) {
30
- const { api, workerFinished } = spawnWorker(jamNetwork.protocol, jamNetwork.WORKER, config, jamNetwork.NetworkingConfig.Codec);
31
+ const { api, worker, workerFinished } = spawnWorker(jamNetwork.protocol, jamNetwork.WORKER, config, jamNetwork.NetworkingConfig.Codec);
31
32
  return {
32
33
  network: api,
34
+ worker,
33
35
  finish: async () => {
34
36
  await api.sendFinish();
35
37
  api.destroy();
@@ -37,22 +39,26 @@ export async function spawnNetworkWorker(config) {
37
39
  },
38
40
  };
39
41
  }
40
- export async function startNetwork(config) {
42
+ export async function startNetwork(config, authorshipPort) {
41
43
  const { api, internal } = startSameThread(jamNetwork.protocol);
42
- const networkFinish = jamNetwork.main(config, internal);
44
+ const authorshipComms = Channel.rx(protocol, authorshipPort);
45
+ const networkFinish = jamNetwork.main(config, internal, authorshipComms);
43
46
  return {
44
47
  network: api,
48
+ worker: null,
45
49
  finish: async () => {
46
50
  await api.sendFinish();
47
51
  api.destroy();
48
52
  await networkFinish;
53
+ authorshipComms.destroy();
49
54
  },
50
55
  };
51
56
  }
52
57
  export async function spawnBlockGeneratorWorker(config) {
53
- const { api, workerFinished } = spawnWorker(blockAuthorship.protocol, blockAuthorship.WORKER, config, blockAuthorship.BlockAuthorshipConfig.Codec);
58
+ const { api, worker, workerFinished } = spawnWorker(blockAuthorship.protocol, blockAuthorship.WORKER, config, blockAuthorship.BlockAuthorshipConfig.Codec);
54
59
  return {
55
60
  generator: api,
61
+ worker,
56
62
  finish: async () => {
57
63
  await api.sendFinish();
58
64
  api.destroy();
@@ -60,15 +66,18 @@ export async function spawnBlockGeneratorWorker(config) {
60
66
  },
61
67
  };
62
68
  }
63
- export async function startBlockGenerator(config) {
69
+ export async function startBlockGenerator(config, networkingPort) {
64
70
  const { api, internal } = startSameThread(blockAuthorship.protocol);
65
- const finish = blockAuthorship.main(config, internal);
71
+ const networkingComms = Channel.tx(protocol, networkingPort);
72
+ const finish = blockAuthorship.main(config, internal, networkingComms);
66
73
  return {
67
74
  generator: api,
75
+ worker: null,
68
76
  finish: async () => {
69
77
  await api.sendFinish();
70
78
  api.destroy();
71
79
  await finish;
80
+ networkingComms.destroy();
72
81
  },
73
82
  };
74
83
  }
@@ -3,6 +3,7 @@ import { ChainSpec } from "#@typeberry/config";
3
3
  import { type BlocksDb, type RootDb, type SerializedStatesDb } from "#@typeberry/database";
4
4
  import { Blake2b } from "#@typeberry/hash";
5
5
  import type { WorkerConfig } from "#@typeberry/workers-api";
6
+ import { ThreadPort, type TransferablePort } from "./port.js";
6
7
  /** A worker config that's usable in node.js and uses LMDB database backend. */
7
8
  export declare class LmdbWorkerConfig<T = void> implements WorkerConfig<T, BlocksDb, SerializedStatesDb> {
8
9
  readonly nodeName: string;
@@ -10,12 +11,14 @@ export declare class LmdbWorkerConfig<T = void> implements WorkerConfig<T, Block
10
11
  readonly workerParams: T;
11
12
  readonly dbPath: string;
12
13
  readonly blake2b: Blake2b;
13
- static new<T>({ nodeName, chainSpec, workerParams, dbPath, blake2b, }: {
14
+ readonly ports: Map<string, ThreadPort>;
15
+ static new<T>({ nodeName, chainSpec, workerParams, dbPath, blake2b, ports, }: {
14
16
  nodeName: string;
15
17
  chainSpec: ChainSpec;
16
18
  workerParams: T;
17
19
  dbPath: string;
18
20
  blake2b: Blake2b;
21
+ ports?: Map<string, ThreadPort>;
19
22
  }): LmdbWorkerConfig<T>;
20
23
  /** Restore node config from a transferable config object. */
21
24
  static fromTransferable<T>(decodeParams: Decode<T>, config: TransferableConfig): Promise<LmdbWorkerConfig<T>>;
@@ -32,6 +35,7 @@ export type TransferableConfig = {
32
35
  chainSpec: ChainSpec;
33
36
  workerParams: Uint8Array;
34
37
  dbPath: string;
38
+ workerPorts: [string, TransferablePort][];
35
39
  };
36
40
  /**
37
41
  * In-memory (direct) worker using serialized state database.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAW,KAAK,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,MAAM,EACX,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,+EAA+E;AAC/E,qBAAa,gBAAgB,CAAC,CAAC,GAAG,IAAI,CAAE,YAAW,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC;aAiC5E,QAAQ,EAAE,MAAM;aAChB,SAAS,EAAE,SAAS;aACpB,YAAY,EAAE,CAAC;aACf,MAAM,EAAE,MAAM;aACd,OAAO,EAAE,OAAO;IApClC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,MAAM,EACN,OAAO,GACR,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,YAAY,EAAE,CAAC,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;KAClB;IAID,6DAA6D;WAChD,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB;IAcpF,OAAO;IAQP,YAAY,CAAC,OAAO,GAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAuB,GAAG,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAUvG,6DAA6D;IAC7D,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,kBAAkB;CAQ7D;AAED,6DAA6D;AAC7D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,UAAU,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,iBAAiB,CAAC,CAAC,GAAG,SAAS,CAAE,YAAW,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC;aAmBlF,QAAQ,EAAE,MAAM;aAChB,SAAS,EAAE,SAAS;aACpB,YAAY,EAAE,CAAC;aACf,OAAO,EAAE,OAAO;IArBlC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,OAAO,GACR,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,YAAY,EAAE,CAAC,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;KAClB;IAID,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAElD,OAAO;IAUP,YAAY,CAAC,QAAQ,GAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAuB,GAAG,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC;CAQzG"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAW,KAAK,MAAM,EAAW,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,KAAK,QAAQ,EAGb,KAAK,MAAM,EACX,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE9D,+EAA+E;AAC/E,qBAAa,gBAAgB,CAAC,CAAC,GAAG,IAAI,CAAE,YAAW,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC;aAuC5E,QAAQ,EAAE,MAAM;aAChB,SAAS,EAAE,SAAS;aACpB,YAAY,EAAE,CAAC;aACf,MAAM,EAAE,MAAM;aACd,OAAO,EAAE,OAAO;aAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IA3ChD,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,MAAM,EACN,OAAO,EACP,KAAiB,GAClB,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,YAAY,EAAE,CAAC,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;KACjC;IAID,6DAA6D;WAChD,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAkB;IAkBpF,OAAO;IASP,YAAY,CAAC,OAAO,GAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAuB,GAAG,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IAUvG,6DAA6D;IAC7D,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,kBAAkB;CAS7D;AAED,6DAA6D;AAC7D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,UAAU,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAAE,CAAC;CAC3C,CAAC;AAEF;;;;GAIG;AACH,qBAAa,iBAAiB,CAAC,CAAC,GAAG,SAAS,CAAE,YAAW,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC;aAmBlF,QAAQ,EAAE,MAAM;aAChB,SAAS,EAAE,SAAS;aACpB,YAAY,EAAE,CAAC;aACf,OAAO,EAAE,OAAO;IArBlC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,OAAO,GACR,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,YAAY,EAAE,CAAC,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;KAClB;IAID,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAElD,OAAO;IAUP,YAAY,CAAC,QAAQ,GAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAuB,GAAG,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC;CAQzG"}
@@ -3,6 +3,7 @@ import { ChainSpec } from "#@typeberry/config";
3
3
  import { InMemoryBlocks, InMemorySerializedStates, } from "#@typeberry/database";
4
4
  import { LmdbBlocks, LmdbRoot, LmdbStates } from "#@typeberry/database-lmdb";
5
5
  import { Blake2b } from "#@typeberry/hash";
6
+ import { ThreadPort } from "./port.js";
6
7
  /** A worker config that's usable in node.js and uses LMDB database backend. */
7
8
  export class LmdbWorkerConfig {
8
9
  nodeName;
@@ -10,28 +11,32 @@ export class LmdbWorkerConfig {
10
11
  workerParams;
11
12
  dbPath;
12
13
  blake2b;
13
- static new({ nodeName, chainSpec, workerParams, dbPath, blake2b, }) {
14
- return new LmdbWorkerConfig(nodeName, chainSpec, workerParams, dbPath, blake2b);
14
+ ports;
15
+ static new({ nodeName, chainSpec, workerParams, dbPath, blake2b, ports = new Map(), }) {
16
+ return new LmdbWorkerConfig(nodeName, chainSpec, workerParams, dbPath, blake2b, ports);
15
17
  }
16
18
  /** Restore node config from a transferable config object. */
17
19
  static async fromTransferable(decodeParams, config) {
18
20
  const blake2b = await Blake2b.createHasher();
19
21
  const chainSpec = new ChainSpec(config.chainSpec);
20
22
  const workerParams = Decoder.decodeObject(decodeParams, config.workerParams, chainSpec);
23
+ const ports = new Map(config.workerPorts.map(([name, port]) => [name, ThreadPort.fromTransferable(chainSpec, port)]));
21
24
  return LmdbWorkerConfig.new({
22
25
  nodeName: config.nodeName,
23
26
  chainSpec,
24
27
  workerParams,
25
28
  dbPath: config.dbPath,
26
29
  blake2b,
30
+ ports,
27
31
  });
28
32
  }
29
- constructor(nodeName, chainSpec, workerParams, dbPath, blake2b) {
33
+ constructor(nodeName, chainSpec, workerParams, dbPath, blake2b, ports) {
30
34
  this.nodeName = nodeName;
31
35
  this.chainSpec = chainSpec;
32
36
  this.workerParams = workerParams;
33
37
  this.dbPath = dbPath;
34
38
  this.blake2b = blake2b;
39
+ this.ports = ports;
35
40
  }
36
41
  openDatabase(options = { readonly: true }) {
37
42
  const lmdb = new LmdbRoot(this.dbPath, options.readonly);
@@ -48,6 +53,7 @@ export class LmdbWorkerConfig {
48
53
  chainSpec: this.chainSpec,
49
54
  workerParams: Encoder.encodeObject(paramsCodec, this.workerParams, this.chainSpec).raw,
50
55
  dbPath: this.dbPath,
56
+ workerPorts: Array.from(this.ports.entries()).map(([name, port]) => [name, port.intoTransferable()]),
51
57
  };
52
58
  }
53
59
  }
@@ -7,12 +7,20 @@ export type Message = {
7
7
  responseId: string;
8
8
  data: Uint8Array;
9
9
  };
10
+ /** Transferable representation of a `ThreadPort`. */
11
+ export type TransferablePort = {
12
+ threadId: number;
13
+ port: MessagePort;
14
+ };
10
15
  export declare class ThreadPort implements Port {
11
16
  private readonly spec;
12
17
  private readonly port;
18
+ static pair(spec: ChainSpec): [ThreadPort, ThreadPort];
13
19
  readonly threadId: number;
14
20
  private readonly events;
15
21
  constructor(spec: ChainSpec, port: MessagePort);
22
+ static fromTransferable(spec: ChainSpec, { port }: TransferablePort): ThreadPort;
23
+ intoTransferable(): TransferablePort;
16
24
  close(): void;
17
25
  private createListener;
18
26
  onClose(callback: (e: Error) => void): void;
@@ -1 +1 @@
1
- {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/port.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,KAAK,KAAK,EAAoB,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAKF,qBAAa,UAAW,YAAW,IAAI;IAKnC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALvB,SAAgB,QAAQ,SAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAG1B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,WAAW;IAcpC,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI3C,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IASvF,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IASzF,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;CAWvE"}
1
+ {"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/api-node/port.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,KAAK,KAAK,EAAoB,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAIF,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AACF,qBAAa,UAAW,YAAW,IAAI;IAWnC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAXvB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC;IAMtD,SAAgB,QAAQ,SAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAG1B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,WAAW;IAcpC,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,gBAAgB;IAInE,gBAAgB,IAAI,gBAAgB;IAIpC,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI3C,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IASvF,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IASzF,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;CAWvE"}
@@ -8,6 +8,10 @@ const logger = Logger.new(import.meta.filename, "workers/api");
8
8
  export class ThreadPort {
9
9
  spec;
10
10
  port;
11
+ static pair(spec) {
12
+ const { port1, port2 } = new MessageChannel();
13
+ return [new ThreadPort(spec, port1), new ThreadPort(spec, port2)];
14
+ }
11
15
  threadId = threadId;
12
16
  events = new EventEmitter();
13
17
  constructor(spec, port) {
@@ -23,6 +27,12 @@ export class ThreadPort {
23
27
  this.events.emit(eventName, responseId, data);
24
28
  });
25
29
  }
30
+ static fromTransferable(spec, { port }) {
31
+ return new ThreadPort(spec, port);
32
+ }
33
+ intoTransferable() {
34
+ return { threadId: this.threadId, port: this.port };
35
+ }
26
36
  close() {
27
37
  this.port.close();
28
38
  }
@@ -1,12 +1,19 @@
1
+ import { AUTHORSHIP_NETWORK_PORT, protocol as networkProtocol } from "#@typeberry/comms-authorship-network";
1
2
  import { Telemetry } from "#@typeberry/telemetry";
3
+ import { Channel } from "#@typeberry/workers-api";
2
4
  import { initWorker } from "#@typeberry/workers-api-node";
3
5
  import { main } from "./main.js";
4
- import { BlockAuthorshipConfig, protocol } from "./protocol.js";
5
- const { config, comms } = await initWorker(protocol, BlockAuthorshipConfig.Codec);
6
+ import { BlockAuthorshipConfig, protocol as mainProtocol } from "./protocol.js";
7
+ const { config, comms } = await initWorker(mainProtocol, BlockAuthorshipConfig.Codec);
6
8
  // Initialize OpenTelemetry for this worker
7
9
  const tele = Telemetry.initialize({
8
10
  worker: "generator",
9
11
  nodeName: config.nodeName,
10
12
  });
11
- await main(config, comms);
13
+ const port = config.ports.get(AUTHORSHIP_NETWORK_PORT);
14
+ if (port === undefined) {
15
+ throw new Error("Network port not found in config");
16
+ }
17
+ const networkingComms = Channel.tx(networkProtocol, port);
18
+ await main(config, comms, networkingComms);
12
19
  await tele?.close();
@@ -1,6 +1,7 @@
1
+ import type { NetworkingComms } from "#@typeberry/comms-authorship-network";
1
2
  import type { WorkerConfig } from "#@typeberry/workers-api";
2
3
  import type { BlockAuthorshipConfig, GeneratorInternal } from "./protocol.js";
3
4
  type Config = WorkerConfig<BlockAuthorshipConfig>;
4
- export declare function main(config: Config, comms: GeneratorInternal): Promise<void>;
5
+ export declare function main(config: Config, comms: GeneratorInternal, networkingComms: NetworkingComms): Promise<void>;
5
6
  export {};
6
7
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/block-authorship/main.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,KAAK,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAK9E,KAAK,MAAM,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAkBlD,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,iBAyMlE"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/block-authorship/main.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAgB3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,KAAK,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAK9E,KAAK,MAAM,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAkBlD,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,iBA2MpG"}
@@ -15,7 +15,7 @@ import { asOpaqueType, assertNever, Result } from "#@typeberry/utils";
15
15
  import { Generator } from "./generator.js";
16
16
  import { generateTickets } from "./ticket-generator.js";
17
17
  const logger = Logger.new(import.meta.filename, "author");
18
- export async function main(config, comms) {
18
+ export async function main(config, comms, networkingComms) {
19
19
  await initWasm();
20
20
  logger.info `🎁 Block Authorship running`;
21
21
  const chainSpec = config.chainSpec;
@@ -134,8 +134,9 @@ export async function main(config, comms) {
134
134
  logger.warn `Failed to generate tickets for epoch ${epoch}: ${ticketsResult.error}`;
135
135
  }
136
136
  else {
137
- logger.log `Generated ${ticketsResult.ok.length} tickets for epoch ${epoch}.`;
138
- // TODO [MaSi]: Sending out tickets
137
+ logger.log `Generated ${ticketsResult.ok.length} tickets for epoch ${epoch}. Distributing...`;
138
+ // Send directly to network worker (bypasses main thread)
139
+ await networkingComms.sendTickets({ epochIndex: epoch, tickets: ticketsResult.ok });
139
140
  }
140
141
  }
141
142
  ticketsGeneratedForEpoch = epoch;
@@ -0,0 +1,3 @@
1
+ export * from "./protocol.js";
2
+ export * from "./tickets-message.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./protocol.js";
2
+ export * from "./tickets-message.js";
@@ -0,0 +1,27 @@
1
+ import { type Api, type Internal } from "#@typeberry/workers-api";
2
+ import { TicketsMessage } from "./tickets-message.js";
3
+ /**
4
+ * Port name for authorship-network direct communication.
5
+ * Used when spawning jam-network worker to pass the port for receiving tickets.
6
+ */
7
+ export declare const AUTHORSHIP_NETWORK_PORT = "authorship-network";
8
+ /**
9
+ * Protocol for direct communication between block-authorship and jam-network workers.
10
+ *
11
+ * This bypasses the main thread for ticket distribution, reducing latency.
12
+ */
13
+ export declare const protocol: import("@typeberry/workers-api").LousyProtocol<{
14
+ tickets: {
15
+ request: import("@typeberry/codec").Descriptor<TicketsMessage, import("@typeberry/codec").ViewOf<TicketsMessage, {
16
+ epochIndex: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<4> & import("@typeberry/utils").WithOpaque<"Epoch">, import("@typeberry/bytes").Bytes<4>>;
17
+ tickets: import("@typeberry/codec").Descriptor<import("@typeberry/block").SignedTicket[], import("@typeberry/codec").SequenceView<import("@typeberry/block").SignedTicket, import("@typeberry/codec").ViewOf<import("@typeberry/block").SignedTicket, {
18
+ attempt: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<1> & import("@typeberry/utils").WithOpaque<"TicketAttempt[0|1|2]">, import("@typeberry/numbers").U8>;
19
+ signature: import("@typeberry/codec").Descriptor<import("@typeberry/bytes").Bytes<784> & import("@typeberry/utils").WithOpaque<"BandersnatchRingSignature">, import("@typeberry/bytes").Bytes<784>>;
20
+ }>>>;
21
+ }>>;
22
+ response: import("@typeberry/codec").Descriptor<void, void>;
23
+ };
24
+ }, {}>;
25
+ export type NetworkingComms = Api<typeof protocol>;
26
+ export type AuthorshipComms = Internal<typeof protocol>;
27
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,GAAG,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,uBAAuB,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;MAUnB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC;AACnD,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { codec } from "#@typeberry/codec";
2
+ import { createProtocol } from "#@typeberry/workers-api";
3
+ import { TicketsMessage } from "./tickets-message.js";
4
+ /**
5
+ * Port name for authorship-network direct communication.
6
+ * Used when spawning jam-network worker to pass the port for receiving tickets.
7
+ */
8
+ export const AUTHORSHIP_NETWORK_PORT = "authorship-network";
9
+ /**
10
+ * Protocol for direct communication between block-authorship and jam-network workers.
11
+ *
12
+ * This bypasses the main thread for ticket distribution, reducing latency.
13
+ */
14
+ export const protocol = createProtocol("authorship-network", {
15
+ // Messages from block-authorship to jam-network
16
+ toWorker: {
17
+ tickets: {
18
+ request: TicketsMessage.Codec,
19
+ response: codec.nothing,
20
+ },
21
+ },
22
+ // Messages from jam-network to block-authorship (none for now)
23
+ fromWorker: {},
24
+ });
@@ -0,0 +1,18 @@
1
+ import type { Epoch } from "#@typeberry/block";
2
+ import { SignedTicket } from "#@typeberry/block/tickets.js";
3
+ import { type CodecRecord } from "#@typeberry/codec";
4
+ import { WithDebug } from "#@typeberry/utils";
5
+ export declare class TicketsMessage extends WithDebug {
6
+ readonly epochIndex: Epoch;
7
+ readonly tickets: SignedTicket[];
8
+ static Codec: import("@typeberry/codec").Descriptor<TicketsMessage, import("@typeberry/codec").ViewOf<TicketsMessage, {
9
+ epochIndex: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<4> & import("@typeberry/utils").WithOpaque<"Epoch">, import("@typeberry/bytes").Bytes<4>>;
10
+ tickets: import("@typeberry/codec").Descriptor<SignedTicket[], import("@typeberry/codec").SequenceView<SignedTicket, import("@typeberry/codec").ViewOf<SignedTicket, {
11
+ attempt: import("@typeberry/codec").Descriptor<number & import("@typeberry/numbers").WithBytesRepresentation<1> & import("@typeberry/utils").WithOpaque<"TicketAttempt[0|1|2]">, import("@typeberry/numbers").U8>;
12
+ signature: import("@typeberry/codec").Descriptor<import("@typeberry/bytes").Bytes<784> & import("@typeberry/utils").WithOpaque<"BandersnatchRingSignature">, import("@typeberry/bytes").Bytes<784>>;
13
+ }>>>;
14
+ }>>;
15
+ static create({ epochIndex, tickets }: CodecRecord<TicketsMessage>): TicketsMessage;
16
+ private constructor();
17
+ }
18
+ //# sourceMappingURL=tickets-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tickets-message.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/comms-authorship-network/tickets-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,KAAK,WAAW,EAAS,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,qBAAa,cAAe,SAAQ,SAAS;aAWzB,UAAU,EAAE,KAAK;aACjB,OAAO,EAAE,YAAY,EAAE;IAXzC,MAAM,CAAC,KAAK;;;;;;QAGT;IAEH,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,WAAW,CAAC,cAAc,CAAC;IAIlE,OAAO;CAMR"}
@@ -0,0 +1,19 @@
1
+ import { SignedTicket } from "#@typeberry/block/tickets.js";
2
+ import { codec } from "#@typeberry/codec";
3
+ import { WithDebug } from "#@typeberry/utils";
4
+ export class TicketsMessage extends WithDebug {
5
+ epochIndex;
6
+ tickets;
7
+ static Codec = codec.Class(TicketsMessage, {
8
+ epochIndex: codec.u32.asOpaque(),
9
+ tickets: codec.sequenceVarLen(SignedTicket.Codec),
10
+ });
11
+ static create({ epochIndex, tickets }) {
12
+ return new TicketsMessage(epochIndex, tickets);
13
+ }
14
+ constructor(epochIndex, tickets) {
15
+ super();
16
+ this.epochIndex = epochIndex;
17
+ this.tickets = tickets;
18
+ }
19
+ }
@@ -1,12 +1,19 @@
1
+ import { AUTHORSHIP_NETWORK_PORT, protocol as authorshipProtocol } from "#@typeberry/comms-authorship-network";
1
2
  import { Telemetry } from "#@typeberry/telemetry";
3
+ import { Channel } from "#@typeberry/workers-api";
2
4
  import { initWorker } from "#@typeberry/workers-api-node";
3
5
  import { main } from "./main.js";
4
- import { NetworkingConfig, protocol } from "./protocol.js";
5
- const { config, comms } = await initWorker(protocol, NetworkingConfig.Codec);
6
+ import { protocol as mainProtocol, NetworkingConfig } from "./protocol.js";
7
+ const { config, comms } = await initWorker(mainProtocol, NetworkingConfig.Codec);
6
8
  // Initialize OpenTelemetry for this worker
7
9
  const tele = Telemetry.initialize({
8
10
  nodeName: config.nodeName,
9
11
  worker: "network",
10
12
  });
11
- await main(config, comms);
13
+ const port = config.ports.get(AUTHORSHIP_NETWORK_PORT);
14
+ if (port === undefined) {
15
+ throw new Error("Authorship network port not found in config");
16
+ }
17
+ const networkingComms = Channel.rx(authorshipProtocol, port);
18
+ await main(config, comms, networkingComms);
12
19
  await tele?.close();
@@ -1,3 +1,4 @@
1
+ import type { AuthorshipComms } from "#@typeberry/comms-authorship-network";
1
2
  import type { WorkerConfig } from "#@typeberry/workers-api";
2
3
  import type { NetworkingConfig, NetworkingInternal } from "./protocol.js";
3
4
  /**
@@ -7,5 +8,5 @@ import type { NetworkingConfig, NetworkingInternal } from "./protocol.js";
7
8
  * (using `typeberry/networking` package) and adding relevant JAMNP-s
8
9
  * stream handlers.
9
10
  */
10
- export declare function main(config: WorkerConfig<NetworkingConfig>, comms: NetworkingInternal): Promise<void>;
11
+ export declare function main(config: WorkerConfig<NetworkingConfig>, comms: NetworkingInternal, authorshipComms: AuthorshipComms): Promise<void>;
11
12
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/jam-network/main.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAI1E;;;;;;GAMG;AACH,wBAAsB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,kBAAkB,iBA0C3F"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../packages/workers/jam-network/main.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAK3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAI1E;;;;;;GAMG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,YAAY,CAAC,gBAAgB,CAAC,EACtC,KAAK,EAAE,kBAAkB,EACzB,eAAe,EAAE,eAAe,iBAmDjC"}
@@ -10,7 +10,7 @@ const logger = Logger.new(import.meta.filename, "net");
10
10
  * (using `typeberry/networking` package) and adding relevant JAMNP-s
11
11
  * stream handlers.
12
12
  */
13
- export async function main(config, comms) {
13
+ export async function main(config, comms, authorshipComms) {
14
14
  await initWasm();
15
15
  logger.trace `🛜 Network starting`;
16
16
  // Await the configuration object
@@ -31,6 +31,13 @@ export async function main(config, comms) {
31
31
  comms.setOnNewHeader(async (header) => {
32
32
  network.syncTask.broadcastHeader(header);
33
33
  });
34
+ // Handle tickets received directly from block-authorship (bypasses main thread)
35
+ authorshipComms.setOnTickets(async ({ epochIndex, tickets }) => {
36
+ logger.log `Received ${tickets.length} tickets directly from block-authorship for epoch ${epochIndex}`;
37
+ for (const ticket of tickets) {
38
+ network.ticketTask.addTicket(epochIndex, ticket);
39
+ }
40
+ });
34
41
  await network.network.start();
35
42
  // stop the network when the worker is finishing.
36
43
  await waitForFinish;