@typeberry/lib 0.6.0 → 0.6.1-563c82d

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 (38) hide show
  1. package/package.json +1 -1
  2. package/packages/core/pvm-interpreter-ananas/index.js +1 -1
  3. package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts +7 -0
  4. package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts.map +1 -1
  5. package/packages/jam/jamnp-s/tasks/ticket-distribution.js +37 -4
  6. package/packages/jam/jamnp-s/tasks/ticket-distribution.test.js +40 -1
  7. package/packages/jam/node/main.d.ts.map +1 -1
  8. package/packages/jam/node/main.js +2 -1
  9. package/packages/workers/api-node/config.d.ts +9 -0
  10. package/packages/workers/api-node/config.d.ts.map +1 -1
  11. package/packages/workers/api-node/config.js +10 -0
  12. package/packages/workers/api-node/config.test.d.ts +2 -0
  13. package/packages/workers/api-node/config.test.d.ts.map +1 -0
  14. package/packages/workers/api-node/config.test.js +41 -0
  15. package/packages/workers/api-node/host-environment.d.ts +19 -0
  16. package/packages/workers/api-node/host-environment.d.ts.map +1 -0
  17. package/packages/workers/api-node/host-environment.js +91 -0
  18. package/packages/workers/api-node/index.d.ts +1 -0
  19. package/packages/workers/api-node/index.d.ts.map +1 -1
  20. package/packages/workers/api-node/index.js +1 -0
  21. package/packages/workers/api-node/protocol.d.ts.map +1 -1
  22. package/packages/workers/api-node/protocol.js +7 -3
  23. package/packages/workers/block-authorship/generator.d.ts +22 -3
  24. package/packages/workers/block-authorship/generator.d.ts.map +1 -1
  25. package/packages/workers/block-authorship/generator.js +39 -5
  26. package/packages/workers/block-authorship/generator.test.js +202 -1
  27. package/packages/workers/block-authorship/main.d.ts.map +1 -1
  28. package/packages/workers/block-authorship/main.js +175 -25
  29. package/packages/workers/comms-authorship-network/protocol.d.ts +13 -2
  30. package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -1
  31. package/packages/workers/comms-authorship-network/protocol.js +10 -3
  32. package/packages/workers/comms-authorship-network/tickets-message.d.ts +14 -0
  33. package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -1
  34. package/packages/workers/comms-authorship-network/tickets-message.js +17 -0
  35. package/packages/workers/importer/finality.js +4 -4
  36. package/packages/workers/importer/finality.test.js +186 -168
  37. package/packages/workers/jam-network/main.d.ts.map +1 -1
  38. package/packages/workers/jam-network/main.js +5 -0
@@ -52,218 +52,244 @@ async function buildLinearChain(db, parentHash, length) {
52
52
  }
53
53
  describe("DummyFinalizer", () => {
54
54
  describe("linear chain", () => {
55
- it("should return null when chain is shorter than depth", async () => {
55
+ it("should return null when chain length is at most 2*depth", async () => {
56
56
  const db = InMemoryBlocks.new();
57
57
  const genesis = db.getBestHeaderHash();
58
58
  const finalizer = DummyFinalizer.create(db, 3);
59
- // Build a chain of 3 blocks: genesis -> 1 -> 2 -> 3
60
- const chain = await buildLinearChain(db, genesis, 3);
61
- // Import all 3 — chain length = depth, not > depth, so no finality.
59
+ // 2*3 = 6 blocks: chain length = 6, not > 6, so no finality.
60
+ const chain = await buildLinearChain(db, genesis, 6);
62
61
  for (const h of chain) {
63
62
  const result = finalizer.onBlockImported(h);
64
63
  assert.strictEqual(result, null);
65
64
  }
66
65
  });
67
- it("should finalize when chain exceeds depth", async () => {
66
+ it("should finalize when chain length exceeds 2*depth", async () => {
68
67
  const db = InMemoryBlocks.new();
69
68
  const genesis = db.getBestHeaderHash();
70
69
  const finalizer = DummyFinalizer.create(db, 3);
71
- // Build: genesis -> 1 -> 2 -> 3 -> 4
72
- const chain = await buildLinearChain(db, genesis, 4);
73
- // First 3 imports: no finality.
74
- for (let i = 0; i < 3; i++) {
70
+ // Need > 2*3=6, so build 7 blocks.
71
+ const chain = await buildLinearChain(db, genesis, 7);
72
+ // First 6 imports: no finality (chain length <= 2*depth).
73
+ for (let i = 0; i < 6; i++) {
75
74
  assert.strictEqual(finalizer.onBlockImported(chain[i]), null);
76
75
  }
77
- // 4th import: chain length = 4 > depth(3), finalize block at index 0.
78
- const result = finalizer.onBlockImported(chain[3]);
76
+ // 7th import: chain length = 7 > 6, finalize chain[3].
77
+ const result = finalizer.onBlockImported(chain[6]);
79
78
  assertExists(result);
80
- assert.strictEqual(result.finalizedHash.isEqualTo(chain[0]), true);
79
+ assert.strictEqual(result.finalizedHash.isEqualTo(chain[3]), true);
81
80
  });
82
- it("should prune the previously finalized block on first finality", async () => {
81
+ it("should prune prev finalized and depth predecessors on first finality", async () => {
83
82
  const db = InMemoryBlocks.new();
84
83
  const genesis = db.getBestHeaderHash();
85
84
  const finalizer = DummyFinalizer.create(db, 3);
86
- const chain = await buildLinearChain(db, genesis, 4);
87
- for (let i = 0; i < 3; i++) {
85
+ const chain = await buildLinearChain(db, genesis, 7);
86
+ for (let i = 0; i < 6; i++) {
88
87
  finalizer.onBlockImported(chain[i]);
89
88
  }
90
- const result = finalizer.onBlockImported(chain[3]);
89
+ const result = finalizer.onBlockImported(chain[6]);
91
90
  assertExists(result);
92
- // Block 1 is finalized. The previously finalized block (genesis) is pruned.
93
- assert.strictEqual(result.prunableStateHashes.length, 1);
91
+ // Prunable: genesis (prev finalized) + chain[0..2] = 4 items.
92
+ assert.strictEqual(result.prunableStateHashes.length, 4);
94
93
  assert.ok(result.prunableStateHashes[0].isEqualTo(genesis));
94
+ for (let i = 0; i < 3; i++) {
95
+ assert.ok(result.prunableStateHashes[i + 1].isEqualTo(chain[i]));
96
+ }
95
97
  });
96
- it("should advance finality one block at a time, pruning previous finalized each time", async () => {
98
+ it("should advance finality in batches of depth blocks", async () => {
97
99
  const db = InMemoryBlocks.new();
98
100
  const genesis = db.getBestHeaderHash();
99
101
  const finalizer = DummyFinalizer.create(db, 2);
100
- // Build: genesis -> 1 -> 2 -> 3 -> 4 -> 5
101
- const chain = await buildLinearChain(db, genesis, 5);
102
- // Import 1, 2: no finality (length <= depth)
103
- assert.strictEqual(finalizer.onBlockImported(chain[0]), null);
104
- assert.strictEqual(finalizer.onBlockImported(chain[1]), null);
105
- // Import 3: length=3 > depth=2, finalize block 1. Prune genesis.
106
- const r1 = finalizer.onBlockImported(chain[2]);
102
+ // depth=2: triggers at > 4 (length >= 5).
103
+ // After trigger: remaining = 2 blocks. Next trigger needs 3 more (length 5).
104
+ // Build enough for 3 triggers: 5 + 3 + 3 = 11 blocks.
105
+ const chain = await buildLinearChain(db, genesis, 11);
106
+ // First 4 imports: no finality (chain length <= 2*depth = 4).
107
+ for (let i = 0; i < 4; i++) {
108
+ assert.strictEqual(finalizer.onBlockImported(chain[i]), null);
109
+ }
110
+ // 5th import: chain length = 5 > 4, finalize chain[2].
111
+ const r1 = finalizer.onBlockImported(chain[4]);
107
112
  assertExists(r1);
108
- assert.strictEqual(r1.finalizedHash.isEqualTo(chain[0]), true);
109
- assert.strictEqual(r1.prunableStateHashes.length, 1);
113
+ assert.strictEqual(r1.finalizedHash.isEqualTo(chain[2]), true);
114
+ assert.strictEqual(r1.prunableStateHashes.length, 3);
110
115
  assert.ok(r1.prunableStateHashes[0].isEqualTo(genesis));
111
- // Import 4: finalize block 2. Prune block 1 (previous finalized).
112
- const r2 = finalizer.onBlockImported(chain[3]);
116
+ assert.ok(r1.prunableStateHashes[1].isEqualTo(chain[0]));
117
+ assert.ok(r1.prunableStateHashes[2].isEqualTo(chain[1]));
118
+ // Next 2 imports: no finality (remaining chain = 2, need > 4).
119
+ assert.strictEqual(finalizer.onBlockImported(chain[5]), null);
120
+ assert.strictEqual(finalizer.onBlockImported(chain[6]), null);
121
+ // 8th import: chain length = 5 > 4, finalize chain[5].
122
+ const r2 = finalizer.onBlockImported(chain[7]);
113
123
  assertExists(r2);
114
- assert.strictEqual(r2.finalizedHash.isEqualTo(chain[1]), true);
115
- assert.strictEqual(r2.prunableStateHashes.length, 1);
116
- assert.ok(r2.prunableStateHashes[0].isEqualTo(chain[0]));
117
- // Import 5: finalize block 3. Prune block 2 (previous finalized).
118
- const r3 = finalizer.onBlockImported(chain[4]);
124
+ assert.strictEqual(r2.finalizedHash.isEqualTo(chain[5]), true);
125
+ assert.strictEqual(r2.prunableStateHashes.length, 3);
126
+ assert.ok(r2.prunableStateHashes[0].isEqualTo(chain[2]));
127
+ assert.ok(r2.prunableStateHashes[1].isEqualTo(chain[3]));
128
+ assert.ok(r2.prunableStateHashes[2].isEqualTo(chain[4]));
129
+ // Next 2 imports: no finality.
130
+ assert.strictEqual(finalizer.onBlockImported(chain[8]), null);
131
+ assert.strictEqual(finalizer.onBlockImported(chain[9]), null);
132
+ // 11th import: chain length = 5 > 4, finalize chain[8].
133
+ const r3 = finalizer.onBlockImported(chain[10]);
119
134
  assertExists(r3);
120
- assert.strictEqual(r3.finalizedHash.isEqualTo(chain[2]), true);
121
- assert.strictEqual(r3.prunableStateHashes.length, 1);
122
- assert.ok(r3.prunableStateHashes[0].isEqualTo(chain[1]));
135
+ assert.strictEqual(r3.finalizedHash.isEqualTo(chain[8]), true);
136
+ assert.strictEqual(r3.prunableStateHashes.length, 3);
137
+ assert.ok(r3.prunableStateHashes[0].isEqualTo(chain[5]));
138
+ assert.ok(r3.prunableStateHashes[1].isEqualTo(chain[6]));
139
+ assert.ok(r3.prunableStateHashes[2].isEqualTo(chain[7]));
123
140
  });
124
- it("should advance finality on every import even when blocks arrive in a burst", async () => {
141
+ it("should not trigger between batch boundaries", async () => {
125
142
  const db = InMemoryBlocks.new();
126
143
  const genesis = db.getBestHeaderHash();
127
144
  const finalizer = DummyFinalizer.create(db, 2);
128
- // Build: genesis -> 1 -> 2 -> 3 -> 4 -> 5
129
145
  const chain = await buildLinearChain(db, genesis, 5);
130
- // Import blocks 1..4 finality fires on block 3 and block 4.
131
- assert.strictEqual(finalizer.onBlockImported(chain[0]), null);
132
- assert.strictEqual(finalizer.onBlockImported(chain[1]), null);
133
- // Block 3: chain [1,2,3] length=3 > depth=2 → finalize block 1.
134
- const r1 = finalizer.onBlockImported(chain[2]);
146
+ // First 4: no finality (chain length <= 4).
147
+ for (let i = 0; i < 4; i++) {
148
+ assert.strictEqual(finalizer.onBlockImported(chain[i]), null);
149
+ }
150
+ // 5th: chain length = 5 > 4, finalize chain[2].
151
+ const r1 = finalizer.onBlockImported(chain[4]);
135
152
  assertExists(r1);
136
- assert.strictEqual(r1.finalizedHash.isEqualTo(chain[0]), true);
137
- // Block 4: chain [2,3,4] length=3 > depth=2 → finalize block 2.
138
- const r2 = finalizer.onBlockImported(chain[3]);
139
- assertExists(r2);
140
- assert.strictEqual(r2.finalizedHash.isEqualTo(chain[1]), true);
141
- // Block 5: chain [3,4,5] length=3 > depth=2 → finalize block 3.
142
- const r3 = finalizer.onBlockImported(chain[4]);
143
- assertExists(r3);
144
- assert.strictEqual(r3.finalizedHash.isEqualTo(chain[2]), true);
153
+ assert.strictEqual(r1.finalizedHash.isEqualTo(chain[2]), true);
145
154
  });
146
155
  });
147
156
  describe("with depth=1", () => {
148
- it("should finalize immediately after 2 blocks", async () => {
157
+ it("should finalize after 3 blocks and prune in batches of 2", async () => {
149
158
  const db = InMemoryBlocks.new();
150
159
  const genesis = db.getBestHeaderHash();
151
160
  const finalizer = DummyFinalizer.create(db, 1);
152
- const chain = await buildLinearChain(db, genesis, 3);
153
- // Import 1: length=1, not > 1. No finality.
161
+ const chain = await buildLinearChain(db, genesis, 6);
162
+ // Import 1: length=1, not > 2. No finality.
154
163
  assert.strictEqual(finalizer.onBlockImported(chain[0]), null);
155
- // Import 2: length=2 > 1. Finalize block 1.
156
- const r = finalizer.onBlockImported(chain[1]);
157
- assertExists(r);
158
- assert.strictEqual(r.finalizedHash.isEqualTo(chain[0]), true);
159
- // Import 3: finalize block 2.
160
- const r2 = finalizer.onBlockImported(chain[2]);
164
+ // Import 2: length=2, not > 2. No finality.
165
+ assert.strictEqual(finalizer.onBlockImported(chain[1]), null);
166
+ // Import 3: length=3 > 2. Finalize chain[1].
167
+ const r1 = finalizer.onBlockImported(chain[2]);
168
+ assertExists(r1);
169
+ assert.strictEqual(r1.finalizedHash.isEqualTo(chain[1]), true);
170
+ // Prunable: genesis + chain[0] = 2 items.
171
+ assert.strictEqual(r1.prunableStateHashes.length, 2);
172
+ assert.ok(r1.prunableStateHashes[0].isEqualTo(genesis));
173
+ assert.ok(r1.prunableStateHashes[1].isEqualTo(chain[0]));
174
+ // Remaining = [chain[2]]. Need > 2, so 1 more block.
175
+ assert.strictEqual(finalizer.onBlockImported(chain[3]), null);
176
+ // Import 5: chain [chain[2], chain[3], chain[4]] length=3 > 2. Finalize chain[3].
177
+ const r2 = finalizer.onBlockImported(chain[4]);
161
178
  assertExists(r2);
162
- assert.strictEqual(r2.finalizedHash.isEqualTo(chain[1]), true);
179
+ assert.strictEqual(r2.finalizedHash.isEqualTo(chain[3]), true);
180
+ assert.strictEqual(r2.prunableStateHashes.length, 2);
181
+ assert.ok(r2.prunableStateHashes[0].isEqualTo(chain[1]));
182
+ assert.ok(r2.prunableStateHashes[1].isEqualTo(chain[2]));
163
183
  });
164
184
  });
165
185
  describe("forks", () => {
166
- it("should track two forks from the finalized block", async () => {
186
+ it("should track two forks and prune dead fork on finality", async () => {
167
187
  const db = InMemoryBlocks.new();
168
188
  const genesis = db.getBestHeaderHash();
169
189
  const finalizer = DummyFinalizer.create(db, 2);
170
- // Fork A: genesis -> A1 -> A2 -> A3
190
+ // Fork A: genesis -> A1 -> A2 -> A3 -> A4 -> A5 (length 5 > 2*2 = triggers)
171
191
  const a1 = await createBlock(db, genesis, 1);
172
192
  const a2 = await createBlock(db, a1, 2);
173
193
  const a3 = await createBlock(db, a2, 3);
194
+ const a4 = await createBlock(db, a3, 4);
195
+ const a5 = await createBlock(db, a4, 5);
174
196
  // Fork B: genesis -> B1 -> B2
175
197
  const b1 = await createBlock(db, genesis, 10);
176
198
  const b2 = await createBlock(db, b1, 11);
177
- // Import A1, A2, B1, B2 — no finality yet.
178
- assert.strictEqual(finalizer.onBlockImported(a1), null);
179
- assert.strictEqual(finalizer.onBlockImported(a2), null);
180
- assert.strictEqual(finalizer.onBlockImported(b1), null);
181
- assert.strictEqual(finalizer.onBlockImported(b2), null);
182
- // Import A3: fork A has length 3 > depth 2. Finalize A1.
183
- const result = finalizer.onBlockImported(a3);
199
+ // Import A1..A4, B1, B2 — no finality.
200
+ for (const h of [a1, a2, a3, a4, b1, b2]) {
201
+ assert.strictEqual(finalizer.onBlockImported(h), null);
202
+ }
203
+ // Import A5: fork A length 5 > 4. Finalize A3 (index 2).
204
+ const result = finalizer.onBlockImported(a5);
184
205
  assertExists(result);
185
- assert.strictEqual(result.finalizedHash.isEqualTo(a1), true);
186
- // Fork B is dead (doesn't contain A1). B1 and B2 should be pruned.
187
- // Also, the previous finalized (genesis) is pruned.
206
+ assert.strictEqual(result.finalizedHash.isEqualTo(a3), true);
207
+ // Fork B [B1, B2] is dead (doesn't contain A3). B1 and B2 should be pruned.
208
+ // Also: genesis (prev finalized) and A1, A2 pruned (before A3 in chain A).
188
209
  const prunedStrings = result.prunableStateHashes.map((h) => h.toString());
189
- assert.ok(prunedStrings.includes(genesis.toString()), "Genesis (prev finalized) should be pruned");
210
+ assert.ok(prunedStrings.includes(genesis.toString()), "Genesis should be pruned");
211
+ assert.ok(prunedStrings.includes(a1.toString()), "A1 should be pruned");
212
+ assert.ok(prunedStrings.includes(a2.toString()), "A2 should be pruned");
190
213
  assert.ok(prunedStrings.includes(b1.toString()), "B1 should be pruned");
191
214
  assert.ok(prunedStrings.includes(b2.toString()), "B2 should be pruned");
192
- // A1 is the finalized block — should NOT be pruned.
193
- assert.ok(!prunedStrings.includes(a1.toString()), "A1 (finalized) should not be pruned");
215
+ assert.ok(!prunedStrings.includes(a3.toString()), "A3 (finalized) should not be pruned");
194
216
  });
195
- it("should keep alive forks that diverge after the finalized block", async () => {
217
+ it("should keep alive forks that diverge at or after the finalized block", async () => {
196
218
  const db = InMemoryBlocks.new();
197
219
  const genesis = db.getBestHeaderHash();
198
220
  const finalizer = DummyFinalizer.create(db, 2);
199
- // Main chain: genesis -> 1 -> 2 -> 3
200
- const b1 = await createBlock(db, genesis, 1);
201
- const b2 = await createBlock(db, b1, 2);
202
- const b3 = await createBlock(db, b2, 3);
203
- // Fork from block 2: 2 -> F1
204
- const f1 = await createBlock(db, b2, 20);
205
- // Import 1, 2 — no finality yet (chain length 2 = depth 2).
206
- finalizer.onBlockImported(b1);
207
- finalizer.onBlockImported(b2);
208
- // Import F1: fork chain [b1, b2, f1] has length 3 > depth 2.
209
- // This triggers finality for b1.
210
- const r1 = finalizer.onBlockImported(f1);
221
+ // Main chain: genesis -> M1 -> M2 -> M3 -> M4
222
+ const m1 = await createBlock(db, genesis, 1);
223
+ const m2 = await createBlock(db, m1, 2);
224
+ const m3 = await createBlock(db, m2, 3);
225
+ const m4 = await createBlock(db, m3, 4);
226
+ // Fork from M3 (the block that will be finalized): M3 -> F1
227
+ const f1 = await createBlock(db, m3, 20);
228
+ // Import M1..M4. F1 creates a fork from the middle of the chain.
229
+ // After importing M1..M4: chain [M1,M2,M3,M4], length 4 not > 4.
230
+ for (const h of [m1, m2, m3, m4]) {
231
+ finalizer.onBlockImported(h);
232
+ }
233
+ // Import F1: parent M3 is at index 2 (not tip M4). Fork: [M1,M2,M3,F1].
234
+ finalizer.onBlockImported(f1);
235
+ // Extend main chain to trigger finality.
236
+ const m5 = await createBlock(db, m4, 5);
237
+ // Import M5: main chain [M1,M2,M3,M4,M5] length 5 > 4. Finalize M3.
238
+ const r1 = finalizer.onBlockImported(m5);
211
239
  assertExists(r1);
212
- assert.strictEqual(r1.finalizedHash.isEqualTo(b1), true);
213
- // Both chains contain b1: main [b1, b2] → alive, trimmed to [b2].
214
- // Fork [b1, b2, f1] alive, trimmed to [b2, f1].
215
- // Only the previous finalized (genesis) is pruned.
216
- assert.strictEqual(r1.prunableStateHashes.length, 1);
217
- assert.ok(r1.prunableStateHashes[0].isEqualTo(genesis));
218
- // Now import b3: it extends the main chain [b2] → [b2, b3].
219
- // Length 2, not > depth 2. No finality.
220
- const r2 = finalizer.onBlockImported(b3);
221
- assert.strictEqual(r2, null);
222
- // Extend the fork: F1 -> F2 -> F3
240
+ assert.strictEqual(r1.finalizedHash.isEqualTo(m3), true);
241
+ // Both chains contain M3:
242
+ // Main [M1,M2,M3,M4,M5]: M3 at index 2. Prune M1,M2. Remaining: [M4,M5].
243
+ // Fork [M1,M2,M3,F1]: M3 at index 2. Prune M1,M2. Remaining: [F1].
244
+ const pruned1 = r1.prunableStateHashes.map((h) => h.toString());
245
+ assert.ok(pruned1.includes(genesis.toString()), "Genesis should be pruned");
246
+ assert.ok(!pruned1.includes(m3.toString()), "M3 (finalized) should not be pruned");
247
+ assert.ok(!pruned1.includes(m4.toString()), "M4 should not be pruned (alive)");
248
+ assert.ok(!pruned1.includes(f1.toString()), "F1 should not be pruned (alive)");
249
+ // Extend fork to trigger next finality round.
223
250
  const f2 = await createBlock(db, f1, 21);
224
251
  const f3 = await createBlock(db, f2, 22);
225
- // Import F2: fork chain becomes [b2, f1, f2], length=3 > depth=2.
226
- // Finalize b2 (index 0). Both chains contain b2, so both are alive.
227
- // unfinalized becomes [[b3], [f1, f2]]. Only prev finalized (b1) pruned.
228
- const r3 = finalizer.onBlockImported(f2);
229
- assertExists(r3);
230
- assert.strictEqual(r3.finalizedHash.isEqualTo(b2), true);
231
- assert.strictEqual(r3.prunableStateHashes.length, 1);
232
- assert.ok(r3.prunableStateHashes[0].isEqualTo(b1));
233
- // Import F3: fork chain becomes [f1, f2, f3], length=3 > depth=2.
234
- // Finalize f1 (index 0). Main chain [b3] doesn't contain f1 → dead.
235
- const r4 = finalizer.onBlockImported(f3);
236
- assertExists(r4);
237
- assert.strictEqual(r4.finalizedHash.isEqualTo(f1), true);
238
- const pruned = r4.prunableStateHashes.map((h) => h.toString());
239
- assert.ok(pruned.includes(b2.toString()), "B2 (prev finalized) should be pruned");
240
- assert.ok(pruned.includes(b3.toString()), "B3 should be pruned (dead fork)");
241
- assert.ok(!pruned.includes(f1.toString()), "F1 should not be pruned (finalized)");
242
- assert.ok(!pruned.includes(f2.toString()), "F2 should not be pruned (after finalized)");
243
- assert.ok(!pruned.includes(f3.toString()), "F3 should not be pruned (after finalized)");
252
+ const f4 = await createBlock(db, f3, 23);
253
+ const f5 = await createBlock(db, f4, 24);
254
+ // [F1] length 1, [M4,M5] length 2. No finality yet.
255
+ assert.strictEqual(finalizer.onBlockImported(f2), null);
256
+ assert.strictEqual(finalizer.onBlockImported(f3), null);
257
+ assert.strictEqual(finalizer.onBlockImported(f4), null);
258
+ // Import F5: fork chain [F1,F2,F3,F4,F5] length 5 > 4. Finalize F3 (index 2).
259
+ // Main chain [M4,M5] doesn't contain F3 → dead fork, prune M4,M5.
260
+ const r2 = finalizer.onBlockImported(f5);
261
+ assertExists(r2);
262
+ assert.strictEqual(r2.finalizedHash.isEqualTo(f3), true);
263
+ const pruned2 = r2.prunableStateHashes.map((h) => h.toString());
264
+ assert.ok(pruned2.includes(m3.toString()), "M3 (prev finalized) should be pruned");
265
+ assert.ok(pruned2.includes(m4.toString()), "M4 should be pruned (dead fork)");
266
+ assert.ok(pruned2.includes(m5.toString()), "M5 should be pruned (dead fork)");
267
+ assert.ok(pruned2.includes(f1.toString()), "F1 should be pruned (before finalized)");
268
+ assert.ok(pruned2.includes(f2.toString()), "F2 should be pruned (before finalized)");
269
+ assert.ok(!pruned2.includes(f3.toString()), "F3 (finalized) should not be pruned");
244
270
  });
245
271
  it("should prune a dead fork that diverged before the finalized block", async () => {
246
272
  const db = InMemoryBlocks.new();
247
273
  const genesis = db.getBestHeaderHash();
248
274
  const finalizer = DummyFinalizer.create(db, 2);
249
- // Main: genesis -> 1 -> 2 -> 3 -> 4
250
- const chain = await buildLinearChain(db, genesis, 4);
275
+ // Main: genesis -> 1 -> 2 -> 3 -> 4 -> 5
276
+ const chain = await buildLinearChain(db, genesis, 5);
251
277
  // Fork from genesis: genesis -> F1 -> F2
252
278
  const f1 = await createBlock(db, genesis, 100);
253
279
  const f2 = await createBlock(db, f1, 101);
254
- // Import: 1, 2, F1, F2, 3 (finalize block 1), 4 (finalize block 2)
255
- finalizer.onBlockImported(chain[0]);
256
- finalizer.onBlockImported(chain[1]);
280
+ // Import main chain and fork no finality yet.
281
+ for (const h of chain.slice(0, 4)) {
282
+ finalizer.onBlockImported(h);
283
+ }
257
284
  finalizer.onBlockImported(f1);
258
285
  finalizer.onBlockImported(f2);
259
- // Import 3: main chain length=3 > depth=2, finalize block 1.
260
- const r1 = finalizer.onBlockImported(chain[2]);
286
+ // Import 5th block: main chain length 5 > 4, finalize chain[2].
287
+ const r1 = finalizer.onBlockImported(chain[4]);
261
288
  assertExists(r1);
262
- assert.strictEqual(r1.finalizedHash.isEqualTo(chain[0]), true);
263
- // Fork [F1, F2] doesn't contain block 1, so it's dead.
264
- // Also, the previous finalized (genesis) is pruned.
289
+ assert.strictEqual(r1.finalizedHash.isEqualTo(chain[2]), true);
290
+ // Fork [F1,F2] doesn't contain chain[2], so it's dead.
265
291
  const pruned1 = r1.prunableStateHashes.map((h) => h.toString());
266
- assert.ok(pruned1.includes(genesis.toString()), "Genesis (prev finalized) should be pruned");
292
+ assert.ok(pruned1.includes(genesis.toString()), "Genesis should be pruned");
267
293
  assert.ok(pruned1.includes(f1.toString()), "F1 should be pruned");
268
294
  assert.ok(pruned1.includes(f2.toString()), "F2 should be pruned");
269
295
  });
@@ -271,40 +297,35 @@ describe("DummyFinalizer", () => {
271
297
  const db = InMemoryBlocks.new();
272
298
  const genesis = db.getBestHeaderHash();
273
299
  const finalizer = DummyFinalizer.create(db, 2);
274
- // Main: genesis -> 1 -> 2 -> 3
300
+ // Main: genesis -> 1 -> 2 -> 3 -> 4
275
301
  const b1 = await createBlock(db, genesis, 1);
276
302
  const b2 = await createBlock(db, b1, 2);
277
303
  const b3 = await createBlock(db, b2, 3);
278
- // Fork from block 1: 1 -> F1 -> F2 -> F3
279
- const f1 = await createBlock(db, b1, 10);
304
+ const b4 = await createBlock(db, b3, 4);
305
+ // Fork from block 2: 2 -> F1 -> F2 -> F3
306
+ const f1 = await createBlock(db, b2, 10);
280
307
  const f2 = await createBlock(db, f1, 11);
281
308
  const f3 = await createBlock(db, f2, 12);
282
309
  // Import main chain first.
283
310
  finalizer.onBlockImported(b1);
284
311
  finalizer.onBlockImported(b2);
285
312
  finalizer.onBlockImported(b3);
286
- // Import fork F1's parent is b1 which is mid-chain, not a tip.
313
+ // Import b4: chain [b1,b2,b3,b4] length 4, not > 4.
314
+ finalizer.onBlockImported(b4);
315
+ // Import fork — F1's parent is b2 which is mid-chain.
287
316
  finalizer.onBlockImported(f1);
288
317
  finalizer.onBlockImported(f2);
289
- // Import F3: fork chain = [b1, f1, f2, f3], length=4 > depth=2.
290
- // Finalize block at index 4-1-2 = 1 → f1.
291
- // But wait — the main chain [b1, b2, b3] also has length 3 > 2.
292
- // Block 3 import already triggered finality for b1.
293
- // After that, main chain trimmed to [b2, b3].
294
- // Fork was created from mid-chain of [b1, b2, b3] at b1.
295
- // But b1 was already part of the chain before finality.
296
- // After finality of b1: unfinalized = [[b2, b3]].
297
- // Now F1's parent is b1 = lastFinalized, so it starts a new chain: [f1].
298
- // F2 extends it: [f1, f2]. F3 extends: [f1, f2, f3]. Length=3 > 2.
299
- // Finalize f1. Main chain [b2, b3] doesn't contain f1 → dead fork.
318
+ // Import F3: fork chain = [b1, b2, f1, f2, f3], length 5 > 4.
319
+ // Finalize block at index 5-1-2 = 2 → f1.
320
+ // Main chain [b1,b2,b3,b4] doesn't contain f1 dead.
300
321
  const r = finalizer.onBlockImported(f3);
301
322
  assertExists(r);
302
323
  assert.strictEqual(r.finalizedHash.isEqualTo(f1), true);
303
324
  const pruned = r.prunableStateHashes.map((h) => h.toString());
304
- // Previous finalized (b1) is pruned, plus main chain [b2, b3] is dead.
305
- assert.ok(pruned.includes(b1.toString()), "B1 (prev finalized) should be pruned");
306
- assert.ok(pruned.includes(b2.toString()), "B2 should be pruned (dead fork)");
325
+ assert.ok(pruned.includes(b1.toString()), "B1 should be pruned");
326
+ assert.ok(pruned.includes(b2.toString()), "B2 should be pruned");
307
327
  assert.ok(pruned.includes(b3.toString()), "B3 should be pruned (dead fork)");
328
+ assert.ok(pruned.includes(b4.toString()), "B4 should be pruned (dead fork)");
308
329
  });
309
330
  });
310
331
  describe("edge cases", () => {
@@ -333,28 +354,25 @@ describe("DummyFinalizer", () => {
333
354
  const db = InMemoryBlocks.new();
334
355
  const genesis = db.getBestHeaderHash();
335
356
  const finalizer = DummyFinalizer.create(db, 2);
336
- // genesis -> 1 -> 2 -> 3 -> 4
337
- const chain = await buildLinearChain(db, genesis, 4);
338
- finalizer.onBlockImported(chain[0]);
339
- finalizer.onBlockImported(chain[1]);
340
- // Block 3 finalizes block 1.
341
- const r1 = finalizer.onBlockImported(chain[2]);
357
+ // genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
358
+ const chain = await buildLinearChain(db, genesis, 7);
359
+ // First 4: no finality.
360
+ for (let i = 0; i < 4; i++) {
361
+ finalizer.onBlockImported(chain[i]);
362
+ }
363
+ // Block 5: chain length 5 > 4, finalize chain[2].
364
+ const r1 = finalizer.onBlockImported(chain[4]);
342
365
  assertExists(r1);
343
- assert.strictEqual(r1.finalizedHash.isEqualTo(chain[0]), true);
344
- // Block 4 finalizes block 2. Block 1 (prev finalized) is pruned.
345
- const r2 = finalizer.onBlockImported(chain[3]);
346
- assertExists(r2);
347
- assert.strictEqual(r2.finalizedHash.isEqualTo(chain[1]), true);
348
- // Block 1 appears exactly once (as previous finalized, not re-finalized).
349
- const pruned = r2.prunableStateHashes.map((h) => h.toString());
350
- assert.strictEqual(pruned.filter((h) => h === chain[0].toString()).length, 1);
351
- // The newly finalized block (chain[1]) should NOT be pruned.
352
- assert.ok(!pruned.includes(chain[1].toString()), "Newly finalized block should not be pruned");
366
+ assert.strictEqual(r1.finalizedHash.isEqualTo(chain[2]), true);
367
+ // Block 6: remaining chain length 2, not > 4. No finality.
368
+ assert.strictEqual(finalizer.onBlockImported(chain[5]), null);
369
+ // Block 7: chain length 3, not > 4. No finality.
370
+ assert.strictEqual(finalizer.onBlockImported(chain[6]), null);
353
371
  });
354
372
  it("should work with depth=0", async () => {
355
373
  const db = InMemoryBlocks.new();
356
374
  const genesis = db.getBestHeaderHash();
357
- // depth=0 means finalize as soon as any block exists.
375
+ // depth=0 means finalize as soon as any block exists (2*0=0, length > 0).
358
376
  const finalizer = DummyFinalizer.create(db, 0);
359
377
  const b1 = await createBlock(db, genesis, 1);
360
378
  // Chain length = 1 > 0 → finalize block at index 1-1-0 = 0 → b1.
@@ -1 +1 @@
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"}
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,iBAyDjC"}
@@ -38,6 +38,11 @@ export async function main(config, comms, authorshipComms) {
38
38
  network.ticketTask.addTicket(epochIndex, ticket);
39
39
  }
40
40
  });
41
+ // Relay tickets received from peers back to block-authorship (one ticket at a time).
42
+ // Returns the validation result so ticket-distribution knows whether to redistribute.
43
+ network.ticketTask.setOnTicketReceived(async (epochIndex, ticket) => {
44
+ return await authorshipComms.sendReceivedTickets({ epochIndex, ticket });
45
+ });
41
46
  await network.network.start();
42
47
  // stop the network when the worker is finishing.
43
48
  await waitForFinish;