@typeberry/lib 0.6.0 → 0.6.1-c1665a2
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.
- package/package.json +2 -2
- package/packages/core/pvm-interpreter-ananas/index.d.ts.map +1 -1
- package/packages/core/pvm-interpreter-ananas/index.js +4 -2
- package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts +7 -0
- package/packages/jam/jamnp-s/tasks/ticket-distribution.d.ts.map +1 -1
- package/packages/jam/jamnp-s/tasks/ticket-distribution.js +37 -4
- package/packages/jam/jamnp-s/tasks/ticket-distribution.test.js +40 -1
- package/packages/jam/node/main.d.ts.map +1 -1
- package/packages/jam/node/main.js +2 -1
- package/packages/workers/api-node/config.d.ts +9 -0
- package/packages/workers/api-node/config.d.ts.map +1 -1
- package/packages/workers/api-node/config.js +10 -0
- package/packages/workers/api-node/config.test.d.ts +2 -0
- package/packages/workers/api-node/config.test.d.ts.map +1 -0
- package/packages/workers/api-node/config.test.js +41 -0
- package/packages/workers/api-node/host-environment.d.ts +19 -0
- package/packages/workers/api-node/host-environment.d.ts.map +1 -0
- package/packages/workers/api-node/host-environment.js +91 -0
- package/packages/workers/api-node/index.d.ts +1 -0
- package/packages/workers/api-node/index.d.ts.map +1 -1
- package/packages/workers/api-node/index.js +1 -0
- package/packages/workers/api-node/protocol.d.ts.map +1 -1
- package/packages/workers/api-node/protocol.js +7 -3
- package/packages/workers/block-authorship/generator.d.ts +22 -3
- package/packages/workers/block-authorship/generator.d.ts.map +1 -1
- package/packages/workers/block-authorship/generator.js +39 -5
- package/packages/workers/block-authorship/generator.test.js +202 -1
- package/packages/workers/block-authorship/main.d.ts.map +1 -1
- package/packages/workers/block-authorship/main.js +175 -25
- package/packages/workers/comms-authorship-network/protocol.d.ts +13 -2
- package/packages/workers/comms-authorship-network/protocol.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/protocol.js +10 -3
- package/packages/workers/comms-authorship-network/tickets-message.d.ts +14 -0
- package/packages/workers/comms-authorship-network/tickets-message.d.ts.map +1 -1
- package/packages/workers/comms-authorship-network/tickets-message.js +17 -0
- package/packages/workers/importer/finality.js +4 -4
- package/packages/workers/importer/finality.test.js +186 -168
- package/packages/workers/jam-network/main.d.ts.map +1 -1
- 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
|
|
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
|
-
//
|
|
60
|
-
const chain = await buildLinearChain(db, genesis,
|
|
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
|
-
//
|
|
72
|
-
const chain = await buildLinearChain(db, genesis,
|
|
73
|
-
// First
|
|
74
|
-
for (let i = 0; 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
|
-
//
|
|
78
|
-
const result = finalizer.onBlockImported(chain[
|
|
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[
|
|
79
|
+
assert.strictEqual(result.finalizedHash.isEqualTo(chain[3]), true);
|
|
81
80
|
});
|
|
82
|
-
it("should prune
|
|
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,
|
|
87
|
-
for (let i = 0; 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[
|
|
89
|
+
const result = finalizer.onBlockImported(chain[6]);
|
|
91
90
|
assertExists(result);
|
|
92
|
-
//
|
|
93
|
-
assert.strictEqual(result.prunableStateHashes.length,
|
|
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
|
|
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
|
-
//
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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[
|
|
109
|
-
assert.strictEqual(r1.prunableStateHashes.length,
|
|
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
|
-
|
|
112
|
-
|
|
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[
|
|
115
|
-
assert.strictEqual(r2.prunableStateHashes.length,
|
|
116
|
-
assert.ok(r2.prunableStateHashes[0].isEqualTo(chain[
|
|
117
|
-
|
|
118
|
-
|
|
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[
|
|
121
|
-
assert.strictEqual(r3.prunableStateHashes.length,
|
|
122
|
-
assert.ok(r3.prunableStateHashes[0].isEqualTo(chain[
|
|
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
|
|
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
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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[
|
|
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
|
|
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,
|
|
153
|
-
// Import 1: length=1, not >
|
|
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 >
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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[
|
|
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
|
|
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,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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(
|
|
186
|
-
// Fork B is dead (doesn't contain
|
|
187
|
-
// Also
|
|
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
|
|
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
|
-
|
|
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 ->
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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(
|
|
213
|
-
// Both chains contain
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
assert.ok(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
assert.strictEqual(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
assert.
|
|
238
|
-
|
|
239
|
-
assert.ok(
|
|
240
|
-
assert.ok(
|
|
241
|
-
assert.ok(
|
|
242
|
-
assert.ok(!
|
|
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,
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
260
|
-
const r1 = finalizer.onBlockImported(chain[
|
|
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[
|
|
263
|
-
// Fork [F1,
|
|
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
|
|
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
|
-
|
|
279
|
-
|
|
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
|
|
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
|
|
290
|
-
// Finalize block at index
|
|
291
|
-
//
|
|
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
|
-
|
|
305
|
-
assert.ok(pruned.includes(
|
|
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,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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[
|
|
344
|
-
// Block
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
assert.strictEqual(
|
|
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,
|
|
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;
|